Taking Advantage of Kotlin

1. Introduction

At I/O 2017, Google announced official support for Kotlin for developing Android apps. Kotlin is a language developed by Jetbrains, with a quickly growing developer base because of its concise, elegant syntax, and 100% interoperability with Java.

What you will build

In this codelab, you will convert an address book application written in the Java language to Kotlin. In doing so, you will see how Kotlin can:

  • Help reduce boilerplate code.
  • Provide concise, readable code that is easy to maintain.
  • Avoid common Android development pitfalls.
  • Enforce best practices as you write the code, preventing runtime errors.

Screenshot_1502311443.png

What you'll learn

  • How to use the Android Studio's Java to Kotlin converter.
  • How to write code using Kotlin syntax.
  • How to use Kotlin lambda expressions and extension functions to reduce boilerplate code and avoid common errors.
  • How to use built-in standard library functions to extend existing Java class functions.
  • How Kotlin can help avoid the Billion Dollar Mistake, the NullPointerException.

What you'll need

  • Latest version of Android Studio
  • An Android device or emulator to run the app on
  • The sample code (downloaded in the next step)
  • Basic knowledge of developing Android apps in the Java language

2. Getting set up

Download the Code

Click the following link to download all the code for this codelab:

Alternatively, you can also find it in this GitHub repository:

$ git clone https://github.com/android/codelab-android-using-kotlin

Unpack the downloaded zip file. The zip file contains a root folder (android-using-kotlin-master), which includes a folder for each step of this codelab.

  • MyAddressBook-starter contains the starter app
  • Steps 1 and 2 are the introduction and getting started steps, so there are no folders for these ones.
  • MyAddressBook-stepN folders contain the app in the finished state after step N.
  • The final app can be found in the MyAddressBook-final folder.

App Overview

MyAddressBook is a simplified address book app that displays a list of contacts in a RecyclerView. You'll be converting all of the Java code into Kotlin, so take a moment to familiarize yourself with the existing code in the MyAddressBook-starter app.

  1. Open MyAddressBook-starter in Android Studio.
  2. Run it.

Contact.java

The Contact class is a simple Java class that contains data. It is used to model a single contact entry and contains three fields: first name, last name and email.

ContactsActivity.java

The ContactsActivity shows a RecyclerView that displays an ArrayList of Contact objects. You can add data in two ways:

  1. Pressing the Floating Action Button will open up a New Contact dialog, allowing you to enter contact information.
  2. You can generate a list of 20 mock contacts quickly by selecting the Generate option in the options menu, which will use an included JSON file to generate Contact objects.

Once you have some contacts, you can swipe on a row to delete that contact, or else clear the whole list by selecting Clear in the options menu.

You can also tap on a row to edit that contact entry. In the editing dialog the first and last name fields are disabled, since for this tutorial app, these fields are immutable (only getters are provided in the Contact class), but the email field is modifiable.

The New Contact dialog performs validation in the following ways:

  • The first and last name fields must not be empty.
  • The email field must contain an email address using a valid format.
  • Attempting to save an invalid contact will show an error in a Toast message.

Configure Kotlin

Before you can use the Kotlin tools and methods, you must configure Kotlin in your project.

  1. In Android Studio, select Tools > Kotlin > Configure Kotlin Plugin Updates. In the window select Stable channel and click on Check for updates now. If there are any updates available click Install.
  2. In Android Studio, select Tools > Kotlin > Configure Kotlin in Project. If a window titled Choose Configurator appears, select Android with Gradle, make sure All modules is selected, and click OK.

Android Studio will add the required dependencies in both the app level and the project level build.gradle files.

  1. You will be prompted to sync the project with Gradle as the build.gradle files have changed. Once the sync is complete, move to the next step and write some Kotlin code.

3. Kotlin Conversion Basics

Convert the Contact POJO to a Kotlin Data Class

Problems with the Contact class file

The Contact.java class should look pretty familiar, as it contains standard POJO code:

  • Private fields, in this case three strings.
  • A constructor to initially set those fields.
  • Getters for the fields you want to obtain externally, in this case all of the fields.
  • Setters for the fields you want to set externally, in this case just the email field.

This kind of object can cause some problems to the unwary developer, because it leaves a few questions open:

  1. Nullability: which of these fields can be null? The fact that the first name and last name can only be set through the constructor and don't have setter methods implies that they are meant to be non nullable, but this is not a guarantee: one could still pass null into the constructor for one of the fields. The email does have a setter, so for this one there is no way to know whether it should be nullable or not.
  2. Mutability: which of these fields might change? Because only the email field has a setter defined, the implication is that only that field is mutable.

The fact that the Java language does not force you to consider the potential null cases, as well as the setting of fields meant to be read-only, can lead to runtime errors such as the dreaded NullPointerException.

Kotlin helps to solve these problems by forcing the developer to think about them while the class is being written, and not at runtime.

Use the converter

  1. Select Contact.java in the Project pane.
  2. Select Code > Convert Java File to Kotlin File. cf230175189cdf67.png
  3. A dialog appears that warns you that there is code in the rest of the project that may require some corrections to work with the conversion (the Contact class is used in the activity). Click OK to make those changes.

The converter runs and changes the Contact.java file extension to .kt. All of the code for the Contact class reduces to the following single line:

internal class Contact(val firstName: String, val lastName: String, var email: String?)

In summary, this class declaration does the following:

  1. Declares a class called Contact.
  2. Declares three properties for the class: two read-only fields for the first and last name, and one mutable variable for the email address.
  3. The firstName and lastName properties are guaranteed never to be null, since they are of type String. The converter can guess this because the Java code did not have setters or secondary constructors that don't set all three properties.
  4. The email field may be null, since it is of type String?.

Kotlin Data Classes

  1. Run the app, and use the Generate menu option in the app to create some contacts. If you want to reset the app and clear your contacts, choose Clear from the Options menu.
  2. In your code, the generateContacts() method uses the toString() method to log each contact being created. Check the logs to see the output of this statement. The default toString() method uses the object's hashcode to identify the object, but it doesn't give you any useful information about the object's contents:
generateContacts: com.example.android.myaddressbook.Contact@d293353
  1. In your Contact.kt file, add the data keyword after the internal visibility modifier.
internal data class Contact
(val firstName: String, val lastName: String, var email: String?)
  1. Run the app again and generate contacts. Check your logs for the much more informative output.

Convert ContactsActivity to Kotlin

The next step is to convert the ContactsActivity to Kotlin. In this process you will learn about some of the limitations of the converter, and some new Kotlin keywords and syntax.

  1. Run the converter on ContactsActivity, using the same process as you did for the Contact.java file (Code > Convert Java File to Kotlin File).

The lateinit keyword

  1. Note that the conversion process changed the Java member variables such as mContacts into Kotlin properties.
private var mContacts: ArrayList<Contact>? = null

All of these properties are marked as nullable except the boolean, since they are not initialized with any values until onCreate() is executed and are therefore null when the activity is created. This is not ideal, since anytime you want to use a method or set a property, you will have to check if the reference is null first (otherwise the compiler will throw an error to avoid a possible NullPointerException).
In Android apps, you usually initialize member variables in an activity lifecycle method, such as onCreate(), rather than when the instance is instantiated.

Fortunately, Kotlin has a keyword for precisely this situation: lateinit. Use this keyword when you know the value of a property will not be null once it is initialized.

  1. Add the lateinit keyword after the private visibility modifier to all of the member variables except the initialized boolean. Remove the null assignment, and change the type to be not nullable by removing the ?.
private lateinit var mContacts: ArrayList<Contact>
  1. Remove the Boolean property type from the mEntryValid member variable (and the preceding colon), because Kotlin can infer the type from the assignment:
private var mEntryValid = false
  1. Remove all of the occurrences of the !! operator. You can select all occurrences of an existing selection by pressing selecting Edit > Find> Select all occurrences and pressing the backspace key.

Clean up ContactsActivity

Your activity should now work as expected. There are a few changes left to finish cleaning up the converted activity:

  1. In the ViewHolder inner class, the nameLabel variable is faintly underlined. Select the variable and press on the orange lightbulb and select Join declaration and assignment.
  2. Repeat step 1 for the emailLabel variable.

4. Lambdas & Standard Library Extensions

Add lambdas for validation

Kotlin provides support for lambdas, which are functions that are not declared, but passed immediately as an expression. They have the following syntax:

  • A lambda expression is always surrounded by curly braces { }.
  • An arrow (->) separates the function parameters from the function definition.
  • The function's parameters (if any) are declared before the -> . Parameter types may be omitted if they can be inferred.
  • The body of the function is defined after ->.
{ x: Int, y: Int -> x + y }

You can then store these expressions in a variable and reuse them.

In this step, you will modify the afterTextChanged() method to use lambda expressions to validate the user input when adding or modifying a contact. This method uses the setCompoundDrawablesWithIntrinsicBounds() method to set the pass or fail drawable on the right side of the EditText.

1693ea46974da514.png

You will create two lambda expressions for validating the user input and store them as variables.

  1. In the afterTextChanged() method, remove the three booleans that check the validity of the three fields.
  2. Create an immutable variable that stores a lambda expression called notEmpty. The lambda expression takes a TextView as a parameter (EditText inherits from TextView) and returns a Boolean:
val notEmpty: (TextView) -> Boolean
  1. Assign a lambda expression to notEmpty that returns true if the TextView's text property is not empty using the Kotlin isNotEmpty() method:
val notEmpty: (TextView) -> Boolean = { textView -> textView.text.isNotEmpty() }
  1. If a lambda expression only has a single parameter, it can be omitted and replaced with the it keyword. Remove the textView parameter and replace its reference with it in the lambda body:
val notEmpty: (TextView) -> Boolean = { it.text.isNotEmpty() }
  1. Create another lambda with the same signature, and assign it to an isEmail variable. This function checks if the TextView's text property matches the email pattern:
val isEmail: (TextView) -> Boolean = { Patterns.EMAIL_ADDRESS
    .matcher(it.text).matches() }
  1. In each call to EditText.setCompoundDrawablesWithIntrinsicBounds(), remove the deleted boolean inside the if/else statement:
 mFirstNameEdit.setCompoundDrawablesWithIntrinsicBounds(null, null,
                if () passIcon else failIcon, null)
  1. Replace it with the appropriate validation lambda expression and pass in the EditText that needs to be validated:
mFirstNameEdit.setCompoundDrawablesWithIntrinsicBounds(null, null,
                if (notEmpty(mFirstNameEdit)) passIcon else failIcon, null)
  1. Change the mEntryValid boolean to call notEmpty() on the first and last name EditTexts, and call isEmail() on the email EditText:
mEntryValid = notEmpty(mFirstNameEdit) and notEmpty(mLastNameEdit) and  isEmail(mEmailEdit)

Although these changes have not reduced the code much, it is possible to see how these validators can be reused. Using lambda expressions becomes even more useful in combination with higher-order functions, which are functions that take other functions as arguments, which will be discussed in the next section.

Add sorting and reduce boilerplate with standard extension functions

One of the main features of the Kotlin language is extensions, or the ability to add functionality to any external classes (ones that you didn't create). This helps avoid the need for "utility" classes that wrap unmodifiable Java or Android framework classes. For example, if you wanted to add a method to ArrayList to extract any strings that only had integers, you could add an extension function to ArrayList to do so without creating any subclasses.

The standard Kotlin library includes a number of extensions to commonly used Java classes.

Add sorting

In this step, you'll use standard library extension functions to add a sort option to the options menu, allowing the contacts to be sorted by first and last name.

The Kotlin standard library includes the sortBy() extension function for mutable lists, including ArrayLists, that takes a "selector" function as a parameter. This is an example of a higher-order function, a function that takes another function as parameter. The role of this passed in function is to define a natural sort order for the list of arbitrary objects. The sortBy() method iterates through each item of the list it is called on, and performs the selector function on the list item to obtain a value it knows how to sort. Usually, the selector function returns one of the fields of the object that implements the Comparable interface, such as a String or Int.

  1. Create two new menu items in res/menu/menu_contacts.xml, to sort the contacts by first name and last name:
<item
    android:id="@+id/action_sort_first"
    android:orderInCategory="100"
    android:title="Sort by First Name"
    app:showAsAction="never" />
<item
    android:id="@+id/action_sort_last"
    android:orderInCategory="100"
    android:title="Sort by Last Name"
    app:showAsAction="never" />
  1. In the onOptionsItemSelected() method of ContactsActivity.kt, add two more cases to the when statement (similar to the switch in Java), using the IDs for the cases:
R.id.action_sort_first -> {}
R.id.action_sort_last -> {}

For R.id.action_sort_first call the sortBy() method on the mContacts list. Pass in a lambda expression that takes a Contact object as a parameter and returns its first name property. Because the contact is the only parameter, the contact can be referred to as it:

mContacts.sortBy { it.firstName }
  1. The list will be reordered, so notify the adapter that the dataset has changed, and return true inside the first case of the onOptionsItemSelected() method:
{
    mContacts.sortBy { it.firstName }
    mAdapter.notifyDataSetChanged()
    return true
}
  1. Copy the code to the "Sort by Last Name" case, changing the lambda expression to return the last name of the passed in contact:
{
    mContacts.sortBy { it.lastName }
    mAdapter.notifyDataSetChanged()
    return true
}
  1. Run the app. You can now sort the contacts by first and last name from the options menu!

Replace for loops with standard library extensions

The Kotlin standard library adds many extension functions to Collections such as Lists, Sets, and Maps, to allow conversion between them. In this step, you'll simplify the save and load contacts methods to use the conversion extensions.

  1. In the loadContacts() method, set the cursor on the for keyword.
  2. Press the orange light bulb to show the quick fix menu and choose Replace with mapTo(){}.

Android Studio will change the for loop into the mapTo(){} function, another higher-order function that takes two arguments: the collection to turn the receiver parameter (The class you are extending) into, and a lambda expression that specifies how to convert the items of the set into items of the list. Note the it notation in the lambda, referring to the single passed in parameter (the item in the list).

  1. Inline this call into the return statement, as the contacts variable is not useful anymore:
private fun loadContacts(): ArrayList<Contact> {
    val contactSet = mPrefs.getStringSet(CONTACT_KEY, HashSet())!!
    return contactSet.mapTo(ArrayList<Contact>()) { 
        Gson().fromJson(it, Contact::class.java) 
    }
}
  1. Remove the <Contact> parameterization in the first argument, as it can be inferred by the compiler from the lambda in the second argument:
return contactSet.mapTo(ArrayList()) { Gson()
    .fromJson(it, Contact::class.java) }
  1. In the saveContacts() method, set the cursor on the underlined for loop definition.
  2. Select the orange bulb icon to show the quick fix menu and choose Replace with map{}.toSet().

Again, Android Studio replaces the loop with an extension function: this time map{}, which performs the passed in function on each item in the list (Uses GSON to convert it to a string) and then converts the result to a Set using the toSet() extension method.

The Kotlin standard library is full of extension functions, particularly higher-order ones that add functionality to existing data structures by allowing you to pass in lambda expressions as parameters. You can also create your own higher-order extension functions, as you'll see in the next section.

5. Custom Extension Functions

EditText validation extension function

The afterTextChanged() method, which validates the values when you add a new contact, is still longer than it needs to be. It has repeated calls to set the validation drawables, and for each call you have to access the EditText instance twice: once to set the drawable and once to check if the input is valid. You also have to check the validation lambda expressions again to set the mEntryValid boolean.

In this task, you will create an extension function on EditText that performs validation (checks the input and sets the drawable).

  1. Create a new Kotlin file by selecting your package directory in the project browser and clicking on File > New > Kotlin File/Class. Call it Extensions and make sure the Kind field says File.

To create an Extensions function, you use the same syntax as for a regular function, except you preface the function name with the class you wish to extend and a period.

  1. In the Extensions.kt file, create an Extension function on EditText called EditText.validateWith(). It takes three arguments: a drawable for the case where the input is valid, a drawable for when the input is invalid, and a validator function that takes a TextView as a parameter and returns a Boolean (like the notEmpty and isEmail validators you created). This extensions function also returns a Boolean:
internal fun EditText.validateWith
        (passIcon: Drawable?, failIcon: Drawable?, 
         validator: (TextView) -> Boolean): Boolean {
}
  1. Define the method to return the result of the validator function. You can pass this as a parameter to the validator, since the function is an extension on EditText, so this is the instance that the method is called on:
internal fun EditText.validateWith
        (passIcon: Drawable?, failIcon: Drawable?, 
         validator: (TextView) -> Boolean): Boolean {
    return validator(this)
}
  1. In the validateWith() method, call setCompoundDrawablesWithIntrinsicBounds(), passing in null for the top, left, and bottom drawables, and using the if/else syntax with the passed in validator function to select either the pass or fail drawable:
setCompoundDrawablesWithIntrinsicBounds(null, null,
            if (validator(this)) passIcon else failIcon, null)
  1. Back in the ContactsActivity, in the afterTextChanged() method, remove all three calls to setCompoundDrawablesWithIntrinsicBounds().
  2. Change the assignment of the mEntryValid variable to call validateWith() on all three EditTexts, passing in the pass and fail icons, as well as the appropriate validator:
mEntryValid = mFirstNameEdit.validateWith(passIcon, failIcon, notEmpty) and
        mLastNameEdit.validateWith(passIcon, failIcon, notEmpty) and
        mEmailEdit.validateWith(passIcon, failIcon, isEmail)

You can take this one step further by changing the type of notEmpty and isEmail lambda expression to be extensions of the TextView class, rather than passing in an instance of TextView. This way, inside the lambda expression, you are a member the of TextView instance and can therefore call TextView methods and properties without referencing the instance at all.

  1. Change the notEmpty and isEmail type declaration to be an extension of TextView and remove the parameter. Remove the it parameter reference, and use the text property directly:
val notEmpty: TextView.() -> Boolean = { text.isNotEmpty() }
val isEmail: TextView.() -> Boolean = { Patterns.EMAIL_ADDRESS.matcher(text).matches() }
  1. In the validateWith() method in the Extensions.kt file, make the third parameter (the validator function) extend the TextView type rather than have it passed in, and remove the this parameter inside the validator method call:
internal fun EditText.validateWith(passIcon: Drawable?, 
                                          failIcon: Drawable?,
                                          validator: TextView.() -> Boolean): Boolean {
    setCompoundDrawablesWithIntrinsicBounds(null, null,
            if (validator()) passIcon else failIcon, null)
    return validator()
}

When you use a higher-order function, the generated Java bytecode creates an instance of an anonymous class, and calls the passed in function as a member of the anonymous class. This creates performance overhead, as the class needs to be loaded into memory. In the above example, every call to validateWith() in the activity will create an anonymous inner class that wraps the validator function, and calls it when it is needed.

Most of the time, the main reason for using a higher-order function is to specify a call order or location, as in the above example, where the passed in function must be called to determine which drawable to load and again to determine the return Boolean. To prevent these anonymous class instances from being created, you can use the inline keyword when defining a higher-order function. In this case, the body of the inlined function gets copied to the location where it is called and no instance is created.

  1. Add the inline keyword to the validateWith() method declaration.

Default Arguments

Kotlin provides the ability to declare default values for parameters of a function. Then, when calling the function, you can omit the parameters that use their default values, and only pass in the values that you choose using the <variableName> = <value> syntax.

  1. In the validateWith() method in the Extensions.kt file, set the pass and fail drawables as defaults for the first two parameters:
internal inline fun EditText.validateWith
        (passIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.ic_pass),
         failIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.ic_fail),
         validator: TextView.() -> Boolean): Boolean {
  1. In the afterTextChanged() method, remove the drawable variables and the first two arguments of each call to validateWith(). You must preface remaining argument with validator = to let the compiler know which argument you are passing in:
 mEntryValid = mFirstNameEdit.validateWith(validator = notEmpty) and
                mLastNameEdit.validateWith(validator = notEmpty) and
                mEmailEdit.validateWith(validator = isEmail)

The afterTextChanged() method is now much easier to read: it declares two lambda expressions for validation, and passes them in the validateWith() extension function using the default pass and fail drawables.

6. Congratulations!

What we've covered

  • How to use the Android Studio's Java to Kotlin converter.
  • How to write code using Kotlin syntax.
  • How to use Kotlin lambda expressions and extension functions to reduce boilerplate code and avoid common errors.
  • How to use built-in standard library functions to extend existing Java class functions.

Reference

Notable Kotlin syntax changes

The Kotlin converter changes a lot of the code to use Kotlin specific syntax. The following section will point out some of these changes that the converter made to the MyAddressBook app.

Kotlin Properties

In Kotlin, you can access properties of objects directly, using the object.property syntax, without the need for an access method (getters and setters in Java). You can see this in action in many places throughout the ContactsActivity class:

  • In the setupRecyclerView() method, the recyclerView.setAdapter(mAdapter) method is replaced with recyclerView.adapter = mAdapter and viewHolder.getPosition() with viewHolder.position.
  • The EditText setText() and, getText() methods are replaced throughout with the text property. The setEnabled() method is replaced with the isEnabled property.
  • The size of the mContacts list is accessed with mContacts.size throughout.
  • You can access one of the items in the mContacts list using mContacts[index] instead of mContacts.get(index).

Lambda Expressions

Kotlin supports lambda expressions, which is a function that is not declared before it is used, but can be passed immediately as an expression.

{ a, b -> a.length < b.length }

This is especially useful wherever you would use an anonymous inner class that implements a single abstract method, such as inside a setOnClickListener() method on a view. In this case, you can pass a lambda expression directly instead of the anonymous object. The converter does this automatically for every OnClickListener in the ContactsActivity, for example:

fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        showAddContactDialog(-1);
    }
});

turns into

fab.setOnClickListener { showAddContactDialog(-1) }

Destructured declarations

Sometimes it is convenient to destructure an object into a number of variables. For example, it is common in the onBindViewHolder() method of adapter classes to use the fields of an object to populate the ViewHolder with data.

This is made easier in Kotlin with by using destructured declarations, such as the ones the converter creates automatically in the onBindViewHolder() declaration. The order of the deconstructed elements the same as in the original class declaration:

val (firstName, lastName, email) = mContacts[position]

You can then use each property to populate the view:

val fullName = "$firstName $lastName"
holder.nameLabel.text = fullName
holder.emailLabel.text = email

Where to learn more