Chapter 4

What Kotlin Does (and When)

IN THIS CHAPTER

check Making decisions with Kotlin statements

check Repeating actions with Kotlin statements

check Adding exception handling

Human thought centers on nouns and verbs. Nouns are the “stuff,” and verbs are the stuff's actions. Nouns are the pieces, and verbs are the glue. Nouns are, and verbs do. When you use nouns, you say, “book,” “room,” or “stuff.” When you use verbs, you say “Do this,” “Do that,” “Hoist that barge,” or “Lift that bale.”

Kotlin also has nouns and verbs. Kotlin’s nouns include String, List, and Array, along with Android-specific things such as AppCompatActivity, ActionBar, and Bundle. Kotlin’s verbs involve assigning values, choosing among alternatives, repeating actions, and other courses of action.

This chapter covers some of Kotlin’s verbs. In Chapter 5 of this minibook, you bring in the nouns.

Making Decisions (Kotlin if Statements)

When you’re writing computer programs, you’re constantly hitting forks in roads. Did the user correctly type his or her password? If yes, let the user work; if no, kick the bum out. So the Kotlin programming language needs a way of making a program branch in one of two directions. Fortunately, the language has a way: It’s called an if statement. The use of an if statement is illustrated in Listing 4-1. (If you want to play with the code that follows and in other areas of this chapter without having to start Android Studio, you can use an online interpreter, such as the one at https://try.kotlinlang.org/.)

LISTING 4-1: Using an if Statement

fun main(args: Array<String>) {
val a = 2
val b = 4

if (a > b) {
print("a is greater")
} else {
print("b is equal or greater")
}
}

An if statement checks a condition, such as whether a is greater than b. In this case, b is greater, so the output is "b is equal or greater".

An if statement has the following form:

if (condition) {
statements to be executed when the condition is true
} else {
statements to be executed when the condition is false
}

In Listing 4-1, the condition being tested is

(a > b)

The condition in an if statement must be enclosed within parentheses. The condition must be a Boolean expression — an expression whose value is either true or false. (See Chapter 3 of this minibook for information about Java's primitive types, including the Boolean type.) So, for example, the following condition is okay:

if (numberOfTries < 17) {

But the strange kind of condition that you can use in languages such as C++ is not okay:

if (17) { //This is incorrect.

Remember You can omit curly braces when only one statement comes between the condition and the word else. You can also omit braces when only one statement comes after the word else. For example, the following code is okay:

if (a > b)
print("a is greater")
else
print("b is equal or greater")

An if statement can also enjoy a full and happy life without an else part. So the following code forms a complete if statement:

if (a > b)
print("a is greater")

Kotlin also has an if expression. An if expression differs from an if statement in the way that you create and use it. Instead of performing an action within the if statement, you place the result in the variable output of the if expression and then use it as needed. Listing 4-2 shows an example of an if expression.

LISTING 4-2: Using an if Expression

fun main(args: Array<String>) {
val a = 2
val b = 4

val max = if (a > b) {
print("a is greater")
a
} else {
print("b is equal or greater")
b
}

print(" The value of max is ${max}.")
}

In this case, max receives the output of the if expression. When a is greater, it receives the value of a. Likewise, when b is greater, it receives the value of b. However, the output expression need not be a number. For example, you could change the code to make max a Boolean, a Double, or a String, as shown here:

val max: String = if (a > b) {
print("a is greater")
a.toString()
} else {
print("b is equal or greater")
b.toString()
}

Tip An if expression is more useful than an if statement when you need to use the result of a comparison in multiple locations or you need additional flexibility in the outcome of the comparison. Rather than perform the comparison multiple times, especially one in which a conversion is made, you can perform it just once and then use the result as often as needed. Comparisons require computer time, so anytime you can save time by not performing a comparison, you help your application run faster and use resources more efficiently.

Testing for equality

Kotlin has several ways to test for equality (“Is this value the same as that value?”). None of these ways is the first thing you'd think of doing. In particular, to find out whether variable a is equal to variable b, you don't write if (a = b). Instead, you use a double equal sign (==). You write if (a == b). In Kotlin, the single equal sign (=) is reserved for assignment. So val n = 5 means “Let n stand for the value 5.”

Comparing two strings is yet another story. When you compare two strings with one another, you don't want to use the double equal sign. Using the double equal sign would ask, “Is this string stored in exactly the same place in memory as that other string?” That’s usually not what you want to ask. Instead, you usually want to ask, “Does this string have the same characters in the same order in it as that other string?” To ask the second question (the more appropriate question), Kotlin’s String type has a method named equals:

if (response.equals("yes")) {

The equals method compares two strings to see whether they have the same characters in them. In this paragraph's tiny example, the variable response refers to a string, and the text "yes" refers to a string. The condition response.equals("yes") is true if response refers to a string whose letters are 'y', and then 'e', and then 's'.

Like most programming languages, Kotlin has the usual complement of comparison operators (such as < for “less than”) and logical operators (such as && for “and”). For a list of such operators, visit https://kotlinlang.org/docs/reference/keyword-reference.html.

Technical stuff There are times when you do want to verify that two variables point to the same object, a condition called referential equality. In this case, you use the === operator (or the !== operator if you want to verify that they don't point to the same object). For example, in the following code, a and b point to the same object, so the output of the referential equality comparison is true:

fun main(args: Array<String>) {
val a: String = "A String Value"
val b = a
println(a === b)
}

Because Kotlin spends so much time ensuring that null values appear only in the right place, you also use a special !! operator to verify that a nullable object isn't equal to null. This particular kind of check is called an assertion. Kotlin asserts whether a is equal to null. You use this operator before performing a task that would fail with a null value, such as obtaining the length of a string, as shown here:

fun main(args: Array<String>) {
val a: String? = "A String Value"
println(a!!.length)
}

This particular form will raise an exception when a equals null. If you don't want to raise an exception, you use the Elvis operator (?:) form, shown here:

fun main(args: Array<String>) {
val a: String? = null
println(a?.length ?: 0)
}

In this case, Kotlin first checks whether a equals null. If so, it outputs a value of 0, rather than trying to obtain the length of a.

Choosing among many alternatives (Kotlin when statements)

We'll bet you were expecting something about the switch statement here. Most languages use some type of switch statement to choose from many alternatives, but a switch statement can be extremely limited. What a switch statement does is replace a series of if…else statements with a single statement. You compare a single value with one or more constants, and there is only one word for the situation: boring. A when structure is so much more flexible, for these reasons:

  • You can use it as an expression or a statement (returning a value or not).
  • The design is safer in that it's harder to have unexpected outcomes (such as leaving out a break clause).
  • The conditions can be expressions rather than just constants.
  • You have no need for an argument if you don’t have one to provide.

Here’s a basic when statement:

fun main(args: Array<String>) {
val a = 5

when (a) {
0, 1, 2, 3, 4 -> println("Less than 5")
5 -> println(5)
else -> print("Greater than 5")
}
}

The code begins by providing an argument, a, which equals 5. It then checks a against the values 0 through 4 and the precise value 5, and it includes a clause to use when all else fails: else. If you have worked with other languages and relied on a switch statement, you immediately notice that

  • The when statement is more concise.
  • You can separate multiple choices using a comma.

The when statement isn't limited to constants. This version of the previous example uses an expression (a range) in place of individual constants. It also checks to determine whether the argument is an Int:

when (a) {
in 0..4 -> println("Less than 5")
is Int -> println("It's an Int")
5 -> println(5)
else -> print("Greater than 5")
}

Warning You might expect multiple outputs in this case because two of the conditions are true, but you see only the first one, "It's an Int", as output. The order of branches in your when statement is important when it's potentially possible for multiple conditions to be true under certain circumstances. The ability to have multiple true conditions can also lead to bugs, so you need to structure your when structures carefully.

The expression form of when works much like the expression form of if, except you're looking at multiple conditions:

fun main(args: Array<String>) {
val a = 5

val result: Any = when (a) {
in 0..4 -> "Less than 5"
5 -> 5
else -> false
}

println(result)
}

Depending on the value of a, result could contain a String, Int, or Boolean. The use of the Any type enables you to receive the most appropriate value type. Type casting would let you convert the value to what you need in given situations.

Remember The expression form of when always requires an else clause unless the compiler can determine that the other clauses cover every contingency. For example, this expression form of when doesn't require an else clause:

fun main(args: Array<String>) {
val a = false

val result = when (a) {
true -> "The value is true."
false -> "The value is false."
}

println(result)
}

The expression form of when can also appear as part of a function. Consider this example in which the expression form of when forms the body of a function used to check data type:

fun main(args: Array<String>) {
println(checkType(3))
println(checkType(true))
println(checkType("Hello"))
println(checkType(arrayOf(1, 2, 3)))
}

fun checkType(x: Any) = when (x) {
is String -> "Value is a String."
is Int -> "Value is an Int."
is Boolean -> "Value is a Boolean."
else -> "Value isn't a usable type."
}

Another use of when is to check various conditions without an argument, which is quite frankly mind boggling. You use it to choose an action based on likely conditions, with the most likely or most desirable conditions first in the list. Here is a basic example:

fun main(args: Array<String>) {
val a = 5
val b = "Goodbye"
val c = true

when {
a - 1 > 5 -> println("a is greater than 5")
b == "Hello" -> println("Hello There!")
c is Boolean -> println("c is ${c}")
else -> println("None of the conditions is true.")
}
}

In this case, no argument is supplied because none is needed. The code checks each condition in turn and provides the desired output. Note that you can include math or other kinds of expressions as part of the conditions. Kotlin will perform the required manipulation before it determines the truth value of a particular condition. The only real requirement is that the condition eventually valuate to a Boolean.

Repeating Instructions Over and Over Again

In 1966, the company that brings you Head & Shoulders shampoo made history. On the back of the bottle, the directions for using the shampoo read, “Lather, rinse, repeat.” Never before had a complete set of directions (for doing anything, let alone shampooing your hair) been summarized so succinctly. People in the direction-writing business hailed this as a monumental achievement. Directions like these stood in stark contrast to others of the time. (For instance, the first sentence on a can of bug spray read, “Turn this can so that it points away from your face.” Duh!)

Aside from their brevity, the thing that made the Head & Shoulders directions so cool was that, with three simple words, they managed to capture a notion that's at the heart of all instruction-giving — the notion of repetition. That last word, repeat, took an otherwise bland instructional drone and turned it into a sophisticated recipe for action.

The fundamental idea is that when you’re following directions, you don’t just follow one instruction after another. Instead, you take turns in the road. You make decisions (“If HAIR IS DRY, then USE CONDITIONER”), and you go into loops (“LATHER-RINSE and then LATHER-RINSE again”). In application development, you use decision-making and looping all the time.

Remember Looping is somewhat different in Kotlin than other languages you might have used. Kotlin does provide a traditional while loop that you use to repeat pieces of code. Kotlin doesn’t provide a true for looping mechanism like that found in Java and other languages. Instead, it relies on iterators, objects that provide sequential access to collection elements without exposing the underlying collection structure. Kotlin also supports looping through recursion, which is when a function calls itself until it reaches a basic form of a problem (the functional language approach to looping). The result of using the iterator and recursive approaches is the same as using loops, but the techniques differ. The following sections discuss Kotlin loops in specific. Chapter 6 of this minibook tells you about the use of recursion to solve problems using the functional programming method in more detail.

Kotlin while statements

A while statement has the following form:

while (condition) {
statements to be executed
}

You can omit the curly braces when the loop has only one statement to be executed.

A traditional while statement performs a task while a condition remains true. The following loop counts down from 5 through 1:

fun main(args: Array<String>) {
var a = 5

while (a > 0) {
println(a)
--a
}
}

Each time the loop executes, the --a statement reduces the value of a by 1. Note that this is a prefix form; you could also use a-=1 to reduce the value by 1. You don't see a 0 in the output because the condition is a > 0, and the condition is checked before the println(a) statement is executed. The while statement has no expression form, so you won't ever see a variable made equal to a while statement as you can with the if or when expressions.

In an Android app, a content provider feeds a cursor to your code. You can think of the cursor as a pointer to a row in a table. In Listing 4-3, each table row has three entries: an _id, a name, and an amount. Supposedly, the _id uniquely identifies a row, the name is a person's name, and the amount is a huge number of dollars owed to you by that person.

LISTING 4-3: A while Loop

cursor.moveToFirst()

while (!cursor.isAfterLast()) {
val _id: String = cursor.getString(0)
val name: String = cursor.getString(1)
val amount: String = cursor.getString(2)
textViewDisplay.append(_id + " " +
name + " " + amount + " ")
cursor.moveToNext()
}

A cursor's moveToFirst method makes the cursor point to the first row of the table. Regardless of the row a cursor points to, the cursor's moveToNext method makes the cursor point to the next row of the table. The cursor's isAfterLast method returns true when, having tried to move to the next row, it finds that there is no next row.

In Kotlin, an exclamation point (!) means “not,” so while (!cursor.isAfterLast()) means “while it's not true that the cursor has reached past the table's last row …” So the loop in Listing 4-3 repeatedly does the following:

As long as the cursor has not reached past the last row,
get the string in the row's initial column and
make _id refer to that string,
get the string in the row's middle column and
make name refer to that string,
get the string in the row's last column and
make amount refer to that string, and
append these strings to the textViewDisplay, and then
move the cursor to the next row in preparation
for returning to the top of the while statement.

Imagine that a particular cursor's table has 100 rows. Then a processor executes the statements inside Listing 4-3's while loop 100 times. Using the official developer lingo, the processor performs 100 loop iterations.

Technical stuff In Listing 4-3, the characters form an escape sequence. When you put inside a string, you're escaping from the normal course of things by displaying neither a backslash nor a letter n. Instead, in a Java string always means “Go to the next line.” So in Listing 4-3, puts a line break between one _id, name, amount group and the next.

Kotlin do statements

Kotlin provides a second form of the while statement called the do statement. Unlike the while statement, which may execute only once, a do statement will always execute at least one time because the condition is checked at the end of the loop, rather than at the beginning of the loop. A do statement has the following form:

do {
statements to be executed
} while (condition)

The following example shows how the do statement works by setting a variable to a value that won't meet conditions.

fun main(args: Array<String>) {
var a = 0

do {
println(a)
--a
} while (a > 0)
}

The example outputs a 0 but then stops. Even though the value of a at the outset doesn't meet the condition while (a > 0), the loop executes once anyway.

To find a particular row of a cursor's table, you normally do a query. (For straight talk about queries, see Book 4.) You almost never perform a do-it-yourself search through a table's data. But just this once, look at a loop that iterates through row after row. The loop is in Listing 4-4.

LISTING 4-4: Leap Before You Look

cursor.moveToFirst()
name: String = ""

do {
val _id: String = cursor.getString(0)
name = cursor.getString(1)
val amount: String = cursor.getString(2)
textViewDisplay.append(_id + " " +
name + " " + amount + " ")
cursor.moveToNext()
} while (!name.equals("Burd") && !cursor.isAfterLast())

In Listing 4-4, you're looking for a row with the name Burd. (After all, the bum owes you lots of money.) When you enter the loop, the cursor points to the table's first row. Before checking a row for the name Burd, you fetch that first row's data and add the data to the textViewDisplay where the user can see what's going on.

Before you march on to the next row (the next loop iteration), you check a condition to make sure that another row is worth visiting. (Check to make sure that you haven't yet found that Burd guy, and that you haven't moved past the last row of the table.)

Technical stuff To get the code in Listing 4-4 working, you have to move the declaration of name outside the do statement. A declaration that's inside a pair of curly braces (such as the _id, name, and amount declarations in Listing 4-3) cannot be used outside curly braces. So, in Listing 4-4, if you don't move the name declaration outside the loop, Java complains that !name.equals("Burd") is incorrect.

Also note the use of the && (AND) operator. The while (!name.equals("Burd") && !cursor.isAfterLast()) expression has two conditions: !name.equals("Burd") and !cursor.isAfterLast(). Both conditions must be true for the do loop to continue processing. If you had used the || (OR) operator instead, the loop would continue to run as long as one of the two conditions was true.

Arrays in Kotlin

An array is a bunch of values placed in a single object. When working with a Kotlin array, what you're really doing is working with a class that stores information, not a primitive construct as found in other languages. Kotlin arrays are extremely flexible depending on how you declare them. For example, here’s a basic Kotlin array containing mixed types:

fun main(args: Array<String>) {
var myArray = arrayOf(1, 2, true, false, "Hello")
println(myArray[1])
}

The object myArray now contains five members: two Int values, two Boolean values, and a String. To access a particular element in the array, you provide the array name, followed by an index in square brackets. The output of this example is 2 because Kotlin arrays are zero-based. So, myArray[1] is actually the second array element.

An array can also accept input from expressions. Here is an example that fills the variable byThree with the values 1 through 3 and then repeats the process.

fun main(args: Array<String>) {
var byThree = Array(6) {i -> (i % 3) + 1}
println(byThree[3])
}

The output is 1 in this case. The example uses a lambda expression, which you find explained in detail in Chapter 6 of this minibook. The value i is the current index, starting with 0 and going through 5. Therefore, (i % 3) + 1 is the remainder of the index divided by 3 and with 1 added.

Now that you have a basic idea of what arrays are, you can look at a few details. The following sections give some specifics that you'll find handy while creating your Android apps.

Understanding what invariant means

Being able to place anything in an array might seem nice at first, but you might want to limit an array to a specific type. To make this happen, you must define the type as part of the array by placing the type in angle brackets, as shown in the following example:

fun main(args: Array<String>) {
var words: Array<String> = Array<String>(6) {String()}
println("Content of words[0] ${words[0]}")
words[0] = "Hello"
println("Content of words[0] ${words[0]}")
}

Notice that you must define a size for the array or that the initialization arguments must imply a size. Kotlin arrays can’t change size after you create them. To make a larger array, you must first create a new array and then move the current content to the new one. The String array, words, begins with empty strings, which you verify by printing the array content at index 0.

The words array is invariant, which means that it accepts only one kind of input, a String. If you tried to add a line of code like words[1] = 2, the compiler would complain that the entry is of the wrong type. Creating an invariant array means that you can depend on the array to contain a specific type of information, which makes working with the array easier and less error prone.

Technical stuff Note that you might need an array of a certain size and able to hold a specific type, but you might not have content to fill it immediately. If this is the case, you can create a nullable version of the array and fill the missing entries with null values to make it easier to determine when an element contains useful information, as shown here:

fun main(args: Array<String>) {
var words: Array<String?> = Array<String?>(6) {null}
words[0] = "Hello"
words[1] = "Goodbye"
println("Content of words[0] ${words[0]}")
println("Content of words[1] ${words[1]}")
println("Content of words[2] ${words[2] == null}")
}

Because this is a nullable array, as signified by Array<String?>, you can set the initializer to {null}. Until you specifically assign a value to an element, its entry equals null. In this case, words[2] == null returns true because you haven't specifically set words[2] to a String value.

Working with Kotlin arrays

Kotlin arrays have many marvelous features that you can access through functions or properties. The complete list of these functions and properties appears at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-array/index.html. However, here is an example showing some of the more interesting functions that are at your disposal when working with numeric arrays:

fun main(args: Array<String>) {
var numbers: Array<Int> = arrayOf(1, 2, 4, 8, 3, 7, 4, 5)
println(numbers.all({i -> i > 0}))
println(numbers.any({i -> i == 8}))
println(numbers.contains(3))
println(numbers.find({i -> i > 4}))
println(numbers.findLast({i -> i > 4}))

println(numbers.distinct())
numbers.sort()
println(numbers.distinct())

println(numbers.average())
println(numbers.distinct().count())
println(numbers.max())
println(numbers.min())
println(numbers.sum())
}

The functions are grouped into three categories: search, list manipulation, and math. When you need to look for things, you may not always need to look for a specific value, but simply know that the value exists, which is where numbers.all({i -> i > 0}) and numbers.any({i -> i == 8}) come in. These functions help you determine whether all the values meet a minimum criteria or at least one of the values does. The lambda expression {i -> i > 0} says that for each element in the array, compare the element to 0 and if the element is greater than 0, return true. The numbers.contains(3) function looks for a specific value. If you need to find the element where a value appears, you can use functions like numbers.find({i -> i > 4}) and numbers.findLast({i -> i > 4}). Here are the outputs from this section.

true
true
true
8
5

The array functions include a lot of list-manipulation features — possibly more than you can use in a lifetime. However, the ability to find distinct values and to sort the array so that the values are in order are high on the list. Fortunately, you don't have to come up with any odd lambda expressions to use these functions. Here are the outputs:

[1, 2, 4, 8, 3, 7, 5]
[1, 2, 3, 4, 5, 7, 8]

The math functions let you perform statistics on the array. Statistics are useful for all sorts of things, like determining whether item one or item two is a better value in your shopping app. You also use statistics for games and to determine whether fraud is occurring. In some cases, you can combine functions to get unique results, such as numbers.distinct().count(), which counts only the unique entries in a list. Here is the output from this part of the code:

4.25
7
8
1
34

Using Kotlin arrays for primitive types

Because you need to interoperate with Java at times, it would be handy to have an array type that works with primitive variables without the boxing overhead (the time needed to place a primitive variable into an object). In this case, you rely on a specific kind of array that has nothing to do with the standard Kotlin Array object. These primitive arrays take the following form:

var numbers: IntArray = intArrayOf(1, 2, 3, 4)

You can also initialize these arrays to a specific size and specific content using any of these methods:

val numbers1 = IntArray(5)
val numbers2 = IntArray(5) {-1}
val numbers3 = IntArray(5) {i -> i + 1}

In the first case, you get an array of five elements containing zeros. The second case gives you five elements filled with –1. The third case uses a lambda expression to fill the elements with values from 1 through 5.

Remember The primitive arrays don't cover things like String values because these values are already reference types in Java and wouldn’t create any boxing overhead. You can obtain primitive arrays in the following types:

  • ByteArray
  • ShortArray
  • IntArray
  • LongArray
  • FloatArray
  • DoubleArray
  • BooleanArray
  • CharArray

Kotlin's for statements

Kotlin doesn’t have a for statement of the type used with other programming languages in which you define a variable to keep track of a specific number of loop iterations. When the variable meets a certain criterion, the loop stops, so basically you can say, “Execute this loop five times and then stop.” Instead, Kotlin relies on a for statement that uses an iterator, as described in the introduction to this section. Essentially, an iterator makes it easy to access members of an object one item at a time, but you don't have to know how many times at the outset. As with a standard for loop, however, the loop executes a fixed number of times and then stops. The following sections describe how the Kotlin for statements work.

Defining the iterator

A Kotlin for statement has this form:

for (item in collection) print(item)

A collection is any object that provides an iterator. To qualify, the object must possess these features:

  • Have a member or extension function called iterator(). The return type of the iterator() function must provide access to these elements:
    • A next() member or extension function that obtains the next item.
    • A hasNext() member or extension function that returns Boolean indicating whether there is a next item to obtain.
  • All three functions — iterator(), next(), and hasNext() — need to be marked as operator.

Remember The term collection could refer to a Collection, MutableCollection, List, Set, or Map, which are collections in the general sense. A collection can also refer to a range, progression, or sequence. In fact, collection refers to any object that implements the Iterable interface. You can see an example of a custom color range iterator at https://www.baeldung.com/kotlin-custom-range-iterator.

Working with ranges, progressions, sequences

A collection is a generic sort of grouping of items into a single object using the Iterable interface. However, you can get more specific with certain kinds of collections because they follow a particular pattern. Here are three kinds of Kotlin collections normally associated with numeric data that follow a particular pattern:

  • Range: A closed interval in a mathematical sense where the range is defined by a starting and an ending point. The collection is an ordered object for comparable types, such as integers, that implement the Comparable interface. Not all ranges implement the Iterable interface, which means that you can't use them in a for statement without modification. For example, the following code will raise an error if you try to use versionRange in a for loop:

    fun main(args: Array<String>) {
    val versionRange = KotlinVersion(1, 11)..KotlinVersion(1, 30)
    println(KotlinVersion(0, 9) in versionRange) // Displays false
    println(KotlinVersion(1, 14) in versionRange) // Displays true
    }

  • Progression: A special kind of numeric range for types, such as Int, Long, and Char, that provides a starting point, ending point, and non-zero step between values.
  • Sequence: An alternative to iterable collections in which processing occurs lazily, only as needed. There is little difference between an iteration and a sequence for simple collections, but when you're working with collections that require multiple processing steps, using a sequence can improve app performance.

Ranges take all sorts of forms, and you’ll use a lot of them in Android development. However, the simplest ranges are found when working with numbers. The following example shows a range of Int values from 1 through 10 and two methods of printing that range:

val numbers = 1..10

for (number in numbers) println(number)
numbers.forEach({println(it)})

Remember The standard for loop method lets you work with the individual values in a structured way and works best for complex processing. The forEach() function approach lets you perform simple tasks using extremely concise code. Both forms make it easy to iterate through a range of numbers or any other collection.

Progressions specify a step between values in a range. You can define them using a number of methods. Here are two methods of creating a progression as a variable:

val numbers = 2..10 step 2
val numbers = IntProgression.fromClosedRange(2, 10, 2)

In both cases, you specify a starting value, ending value, and a step value. You can also create a progression as part of the for loop:

val numbers = 2..10

for (number in numbers step 2) println(number)
numbers.forEach({i -> if (i % 2 == 0) println(i)})

In the first case, you specify a step value as part of the for loop condition. When working with the forEach() function, you need to specify the step value as part of a lambda expression, as shown here, or use some other method of filtering values. The lambda expression used here selects even values by obtaining the result of the current value mod 2.

You can turn any range into a sequence by using the asSequence() function. Using a sequence can reduce processing steps when working with a multiple-step process. However, after you turn a range into a sequence, you can't work with it as a progression, which means that you can’t specify a step value as part of a for loop. Instead, you need to resort to techniques such as filtering, as shown here:

val numbers = (2..10).asSequence()

for (number in numbers.filter({it % 2 == 0})) {
println(number)
}
numbers.forEach({i -> if (i % 2 == 0) println(i)})

Technical stuff Sometimes you need to resort to clever programming or tricks to get the result you want. For example, even though a KotlinVersion range doesn't allow direct iteration, you can use the following code to obtain a list of major and minor values:

fun main(args: Array<String>) {
val versionRange = KotlinVersion(1, 11)..KotlinVersion(1, 30)

val start = versionRange.start
val end = versionRange.endInclusive

for (major in start.major..end.major) {
for (minor in start.minor..end.minor) {
println("$major.$minor")
}
}
}

Iterating indexes

When working with a standard for loop, you have an index value you use to access each member of the collection. However, using the iterator approach means that you either need to create a counter or simply do without an index value in Kotlin using its approach. Fortunately, you have two methods to work with indexes in Kotlin:

  • withIndex()
  • forEachIndex

These two functions produce about the same results and should have the same performance characteristics. The only difference is in how you implement them. Use the method that works best for you. Here's an example of withIndex():

val numbers = 2..10

for ((index, number) in numbers.withIndex()) {
println("The value $number is at index $index.")
}

You don’t have to do anything special with the range. The only changes are your addition of withIndex() to the range and output values to two variables: index and number. The index is always the first variable, and the value is always the second variable in the output.

The forEachIndex approach does rely on a lambda expression. It looks like this:

val numbers = 2..10

numbers.forEachIndexed{index, number ->
println("The value $number is at index $index.")}

The results are the same as before, but the technique used to obtain them differs. In this case, index and number act as inputs to the lambda expression, but otherwise the code looks the same.

Looping using Kotlin recursion

When working with standard loops, each iteration of the loop depends on the previous iteration. Consequently, it's difficult to perform looping in a multiprocessing environment because of the element of state, the recording of loop conditions, in each iteration. One of the goals of functional programming is to enable you to split up everything in an app so that each part can execute on a different processor. Although this might not seem a very laudable goal with the kinds of examples you see in this book, it’s an essential element of higher-end apps that perform machine learning or deep learning tasks (among other things). In these environments, developers often use GPUs instead of CPUs to perform processing, and some of the GPU cards used contain hordes of processors. (The NVidia Titan V contains 5,120 CUDA core; see https://www.nvidia.com/en-us/titan/titan-v/.) Consequently, you need another way to perform looping, which is where recursion comes into play. However, even in an Android app, the ability to break up a problem into very small pieces for execution on separate processors can be important, especially when it comes to games.

Chapter 6 of this minibook delves more deeply into recursion, but for now, take a look at a simple example:

fun main(args: Array<String>) {
val numbers = (2..10)

LoopIt(numbers, numbers.count() - 1)
}

fun LoopIt(range: Iterable<Any>, posit: Int) {
if (posit >= 0) {
LoopIt(range, posit - 1)
println(range.elementAt(posit))
} else {
println("All elements visited!")
}
}

The actual display of values occurs in the LoopIt() function. The idea is that you can't display multiple range values — you can display only one range value using println(range.elementAt(posit)). So, you must work through the range until you get to the first element and then display each single element in turn until you reach the end. The LoopIt(range, posit - 1) call keeps calling LoopIt() one element position at a time until it reaches the beginning of the range: element 0.

The amazing thing is that each of these calls is separate and the variables are val, which means they can't change. Until the code reaches element 0, the LoopIt(range, posit - 1) call doesn’t return; instead, it keeps moving forward in a circle until it reaches its destination. When the code does reach element 0, it prints the message "All elements visited!". What you see as output is

All elements visited!
2
3
4
5
6
7
8
9
10

Working with break and continue

Sometimes a loop condition that you didn't expect or want to avoid will occur. The condition isn’t necessarily an error, but it’s just not what you wanted. In this case, you need to perform a structural jump. Kotlin provides three such jumps:

  • continue: The loop stops at its current point of processing and continues with the next loop iteration. This type of jump allows processing to continue, even if you want to avoid a particular condition.
  • break: The loop exits at its current point of processing and continues with the next statement in the current function. You use this kind of jump when a condition will make further loop processing pointless, but you want to continue with the current processing instructions.
  • return: The loop exits at its current point of processing and returns to the caller. You use this kind of jump when a condition will prevent further processing in a called function, but the condition isn't necessarily an error — it’s just unexpected or unwanted.

You use continue or break in any loop, whether the code exists in the current function or in a called function. Here's an example of using continue and break in a for loop:

fun main(args: Array<String>) {
val numbers = (2..10)

for (number in numbers) {
if (number == 3) continue
if (number == 8) break
println(number)
}
println("The loop has ended.")
}

The output from this example is

2
4
5
6
7
The loop has ended.

Remember Notice that the println(number) call doesn't occur for 3 because the loop continues with the next iteration: if (number == 3) continue. Even though the loop could continue processing values beyond 7, the loop ends at 7 because of the call to if (number == 8) break. Always remember that break implies that you want to break out of the loop and continue implies that you want to continue processing the loop.

The return jump works a bit differently from continue or break. Here's an example of return:

fun main(args: Array<String>) {
val numbers = (2..10)

processData(numbers)
println("The call has returned.")
}

fun processData(numbers: IntRange) {
for (number in numbers) {
if (number == 8) return
println(number)
}
println("The loop has ended.")
}

In this case, the jump occurs in a called function, processData(). You see the values 2 through 7 output, but when the value reaches 8, the code calls return. The example never displays "The loop has ended.", as it would when break is called. Instead, the entire processData() function ends and the call returns to main().

Warning If the return call had happened in main(), the app would have stopped, which would be confusing to your user, so you should avoid using return unless absolutely necessary.

Jumping Away from Trouble

The Kotlin programming language has a mechanism called exception handling. With exception handling, a program can detect that things are about to go wrong and respond by creating a brand-new object. In the official terminology, the program is said to be throwing an exception. That new object, an instance of the Exception class, is passed like a hot potato from one piece of code to another until some piece of code decides to catch the exception. When the exception is caught, the program executes some recovery code, buries the exception, and moves on to the next normal statement as if nothing had ever happened (at least in theory).

The whole thing is done with the aid of several Kotlin keywords. These keywords are as follows:

  • throw: Creates a new exception object.
  • try: Encloses code that has the potential to create a new exception object. In the usual scenario, the code inside a try clause contains calls to methods whose code can create one or more exceptions.
  • catch: Deals with the exception, buries it, and then moves on.
  • finally: Sometimes you need to execute some code regardless of whether an exception occurs. For example, you might need to perform some cleanup so that when the program recovers because of your exquisite error handling, it doesn't immediately throw another exception.
  • Nothing: This is a special type used to tell the Kotlin compiler what a particular function never returns. It’s an end point when something too terrible for words occurs. When the compiler sees Nothing, it assumes that the app won't continue. Here’s an example:

    fun appFailure(message: String): Nothing {
    throw IllegalArgumentException(message)
    }

Technical stuff If you have worked with Java, you know that Java also supports a throws keyword for checked exceptions. Kotlin doesn’t support checked exceptions, so you won’t see the throws keyword used (see the “Getting rid of the checked exceptions” section of Chapter 2 of this minibook for details).

Kotlin spends a good deal of time looking over your shoulder, so you'll encounter fewer exceptions as you write apps. However, it’s a good idea to make preparations to handle them anyway. In the following example, the code attempts to convert a String value to an Int value:

val myIntStr: String = "Hello"

try {
val myInt = myIntStr.toInt()
println("Success: $myInt!")
} catch (e: NumberFormatException) {
println("myIntStr is in the wrong format.")
} finally {
println("Try another int conversion?")
}

Obviously, a value of "Hello" will never convert to an Int value, so the toInt() function throws a NumberFormatException, which the code catches and then displays "myIntStr is in the wrong format.". However, if you change "Hello" to "2", the conversion does take place and you see "Success: 2!" instead. No matter what happens, however, you always see the final output of "Try another int conversion?".

Tip Now the problem is in trying to determine which exceptions to catch. The Kotlin documentation includes an Exceptions section for each of the functions it supports. So, for example, when you look at the toInt() function documentation at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/to-int.html, you see that the only exception it throws is NumberFormatException.

Unlike other languages, try isn't a statement; it’s an expression. So you can use it as part of any expression you create. Here’s an example of performing an Int conversion that relies on an expression:

val myIntStr: String = "Hello"

val myInt: Int = try { myIntStr.toInt() }
catch (e: NumberFormatException) {
println("myIntStr is in the wrong format.")
0
}

println(myInt)

Remember Because this is an expression, you must return a value to myInt, even if the conversion fails. You could handle this in several ways. The example simply sets the output to 0. However, you could also make myInt nullable and set the output to null, or you could use a special value such as –1 (when no negative values are expected).

Working with Kotlin Collections

A collection is a grouping of items in a specific kind of container. Some containers, such as List, are simple; others, such as Map, are more complicated. The container you choose depends on the complexity of the app environment — that is, how the data in the container will get used. The following sections take a brief look at Kotlin containers.

Considering the collection types

Kotlin doesn’t actually support many native collection types. You have three choices, as described here:

  • List: An ordered collection of items in which item values can appear more than once and each value is accessed by a unique index. You would use a List to hold the individual words in a sentence, for example, when order is important.
  • Set: A collection of unique items in which each value is accessed by an index, but the order of the items is unimportant. You would use a Set to hold the letters of the alphabet or the numbers 0 through 9, for example.
  • Map: A collection of key and value pairs in which the keys are ordered and unique, while the values need not be unique. Each key points to a specific value, even when that value is the same as some other value in the Map. Another term for a Map is a dictionary. You often see a Map used to store logical connections between items, such as locations on a map or the hierarchy in an organization.

However, these three choices aren't the extent of your options. Kotlin provides Java interoperability, so this is one place where you may need to go the Kotlin version of the Java route to make things work. With this idea in mind, you also have these Java-specific options:

ArrayList

LinkedList

Vector

Stack

Queue

PriorityQueue

Deque

ArrayDeque

HashSet

LinkedHashSet

SortedSet

TreeSet

Remember Using a Kotlin-specific implementation when possible is always preferable. In fact, the compiler often warns you when you attempt to use a Java collection when a Kotlin version is available. The Kotlin implementation provides additional flexibility and more safeguards than the Java version. Here is a Kotlin implementation of a Map:

val myMap = mapOf("Yellow" to 1, "Green" to 2, "Blue" to 3)

for ((Key, Value) in myMap) {
println("The value $Value is associated with $Key.")
}

Notice that when you create a Map, you must associate a key, such as "Yellow", with a value, such as 1. Likewise, when iterating through a Map, you obtain both a key and a value for each loop.

As previously mentioned, sometimes you need to use a Java collection when Kotlin doesn't provide what you need. One of the most common Java collections you use is the Stack. A Stack pushes new values into the collection and pops them back off the top, which makes this is a last-in/first-out (LIFO) collection. Here is an example of a Java Stack implemented in Kotlin:

import java.util.Stack

fun main(args: Array<String>) {
val myStack = Stack<String>()

myStack.push("Hello")
myStack.push("Goodbye")
myStack.push("Yesterday")

println(myStack.count())
println(myStack.pop())
}

The example begins by importing the proper collection from java.util, which is a Stack in this case. You use the standard Kotlin approach to creating a new Stack, but you must make sure to specify the data type that will appear in the Stack or the compiler will complain. Theoretically, you can use the Any type with a Java collection.

To add a new item to myStack, you call the push() function with whatever value you want to add. You determine the number of items on the stack by calling count(). To remove an item, you call pop(), and the value is provided as a return.

Differentiating between read-only and mutable collections

By default, Kotlin collections are immutable, meaning read-only. After you create the collection, you can't add, subtract, or change values. For example, the documentation for List at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html contains all sorts of methods for searching, selecting, and otherwise stretching the List in every conceivable direction, but the data remains the same. You can obtain part of the data and put it into a new List, but the current List is immutable.

Remember Making a collection immutable lets you work with the collection in ways that aren't possible when you use a mutable collection. If you know that the collection won’t ever change, you can offload processing to a bunch of processors and not really care which processor does what. When working with huge datasets, the ability to use multiple processors outweighs almost every other consideration. Even with smaller apps of the sort you create for Android, the speed difference of using an immutable collection can be noticeable, but there is the penalty of never being able to change the collection content to pay. Of course, this may be a nonissue if the data you’ve collected, such as a list of states in the U.S., isn’t likely to change. Here’s an example of an immutable List:

val myList = listOf(1, 2, 3, 4, 5)
for (item in myList) println(item)

Tip Oddly enough, using an immutable List is also more secure than using the mutable version. Because no one can modify the List content, it becomes harder for someone to suddenly decide to make your life interesting with unauthorized changes.

A MutableList offers the flexibility of being able to grow or reduce the size of the collection and to change collection items. When you look at the documentation for MutableList at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-list/index.html, you see all sorts of functions to modify the list content. Here is an example of a MutableList:

val myList = mutableListOf(1, 2, 3, 4, 5)
myList.add(6)
myList.set(2, 20)
myList.remove(0)
for (item in myList) println(item)

Here is the output from this example:

1
2
20
4
5
6

All the changes that the code made are shown in the output, as expected. Even with this sort of list, you have limits on what you can do unless you create your code correctly at the outset. For example, myList.add("Hello") would fail with myList in this case. To make it possible to add a string, you'd need to declare myList like this:

val myList: MutableList<Any> = mutableListOf(1, 2, 3, 4, 5)

Notice that the myList is now defined as being of type MutableList<Any>. As you work through your Android app, you have to choose the correct methods of interacting with app data to make your app secure, efficient, and fast. The collection choices that Kotlin provides let you make good choices more easily.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.230.82