16 Kotlin Tips for Android Development

Development Tips Android

Savvy Apps started using Kotlin for its new Android projects late in 2016, right around when Kotlin 1.0.4 was released. Initially, we saw an opportunity to try out Kotlin on a smaller scale project. Once we tried it out and saw the ease of use, being able to easily separate functionality from business logic using extensions, and generally, the hours of development time it saved for us, we decided it would be the language of choice moving forward. Since then, we've used Kotlin to create multiple Android apps and have also developed a number of internal Kotlin libraries.

To build off our experience with Kotlin, we decided to compile our most useful and favorite tips across Savvy's collective Android development team. Before reading these tips, you should check out the Kotlin documentation and explore the language for yourself at try.kotlinlang.org. Since these tips focus specifically on using Kotlin within the context of Android development, you should have experience with the Android SDK as well. You should also be familiar with the Kotlin plugin and using Kotlin with Android Studio, which is provided by JetBrains, the creators of Kotlin.

Note: These tips are ordered based on your familiarity with Kotlin so you can easily skip to tips that suit your skill level.

Initial Kotlin Tips for Android

Lazy Loading

There are several benefits to lazy loading. Lazy loading can result in faster startup time, since loading is deferred to when the variable is accessed. This is particularly useful in using Kotlin for an Android app as opposed to a server app. For Android apps, we want to reduce app startup time so that the user sees the app content faster, rather than sitting at an initial loading screen.

Lazy loading like this is also more memory efficient, as we only load the resource into memory if it is called upon. Memory usage is important on mobile platforms like Android, since phones have limited, shared resources. For example, if you are creating a shopping app, and there is a possibility that users will only browse your selection, you could have the actual purchasing API be lazy loaded:

val purchasingApi: PurchasingApi by lazy {
    val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(API_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    retrofit.create(PurchasingApi::class.java)
}

By using lazy loading like this, if the user never attempts to check out in the app, you will never load the PurchasingApi, and therefore will not use up the resources it would take.

Lazy loading is also a good way to encapsulate initialization logic:

// bounds is created as soon as the first call to bounds is made
val bounds: RectF by lazy { 
    RectF(0f, 0f, width.toFloat(), height.toFloat()) 
}

As soon as the first reference to bounds is made, the RectF is created, using the view's current width and height, saving us from having to explicitly create this RectF, then set it later on.

Custom Getters/Setters

Kotlin's custom getters and setters use the structure of a model, but specify custom behavior to get and set the fields. When using custom models for certain frameworks, such as the Parse SDK, you are fetching values that are not actually local variables in the class, but are stored and retrieved in some custom way, such as from JSON. By using custom defined getters and setters, we can simplify the access:

@ParseClassName("Book")
class Book : ParseObject() {

    // getString() and put() are methods that come from ParseObject
    var name: String
        get() = getString("name")
        set(value) = put("name", value)

    var author: String
        get() = getString("author")
        set(value) = put("author", value)
}

Fetching these values would look similar to using property access syntax with other models:

val book = api.getBook()
textAuthor.text = book.author

Now if your model needed to change from Parse to some other data source, your code would potentially only need to be changed in one place.

Lambdas

Lambdas reduce the overall lines of code in a source file and allow for functional programming. While lambdas are currently possible with Android, Kotlin takes them a step further by ensuring you don't have to deal with Retrolambda or changing the way your build is configured.

For example, an on-click listener would look like:

button.setOnClickListener { view ->
    startDetailActivity()
}

It even works with return values:

toolbar.setOnLongClickListener { 
    showContextMenu()
    true
}

The Android SDK contains plenty of cases where you are setting a listener or implementing a single method. Lambdas work great in those circumstances.

Data Classes

Data classes simplify classes, adding equals(), hashCode(), copy(), and toString() methods automatically. They clarify the intention of the model and what should go in it, separating pure data from business logic.

Take a look at this data class as an example:

data class User(val name: String, val age: Int)

That's it. Nothing else is needed to make this class work. If you are using data classes with something like Gson or another JSON parsing library, you can create the default constructor with default values like so:

// Example with Gson's @SerializedName annotation
data class User(
    @SerializedName("name") val name: String = "",
    @SerializedName("age") val age: Int = 0
)
Collection Filtering

Collections are dealt with quite often when working with an API. More often then not, you want to filter or modify the contents of that collection. By using Kotlin's collection filtering, adding clarity and making your code more succinct. It's easier to tell what your resulting list should contain with collection filtering like the following:

val users = api.getUsers()
// we only want to show the active users in one list
val activeUsersNames = items.filter { 
    it.active // the "it" variable is the parameter for single parameter lamdba functions
}
adapter.setUsers(activeUsers)

Filtering a collection using the built-in Kotlin methods is very comparable to other functional programming languages too, such as Java 8 streams or Swift collection types. Being able to filter collections in a unified way helps when talking with team members about what operations need to be done to get a list down to the right elements to display.

Object Expressions

Object expressions allow for strict singleton definition so there's no mistaking it for a class that can be instantiated. They also ensure that you do not have to store singletons somewhere like in the Application class or as a static class variable.

For example, if I have a utility class with static thread-related methods I want to access throughout the app:

package com.savvyapps.example.util

import android.os.Handler
import android.os.Looper

// notice that this is object instead of class
object ThreadUtil {

    fun onMainThread(runnable: Runnable) {
        val mainHandler = Handler(Looper.getMainLooper())
        mainHandler.post(runnable)
    }
}

ThreadUtil is called later in the typical way you would call a static class method:

ThreadUtil.onMainThread(runnable)

This means there's no more declaring a constructor as private, or having to figure out where the static instance is stored. Objects are essentially first class citizens of the language. In a similar way, we create objects instead of anonymous inner classes:

viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
    override fun onPageScrollStateChanged(state: Int) {}

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}

    override fun onPageSelected(position: Int) {
        bindUser(position)
    }
});

Both of these do essentially the same thing -- create a single instance of a class as a declared object.

Companion Object

At a glance, Kotlin appears to be missing static variables and methods. In a sense, it does not have these concepts, but instead has the idea of companion objects. These companion objects are singleton objects within a class which contain the methods and variables you might want to access in a static fashion. A companion object allows defined constants and methods, similar to static in Java. With it, you can follow the newInstance pattern of fragments.

Here's a look at a companion object in its simplest form:

class User {

    companion object {
        const val DEFAULT_USER_AGE = 30
    }
}

// later, accessed like you would a static variable:
user.age = User.DEFAULT_USER_AGE

In Android, we commonly use static methods and variables to create static factories for fragments or activity intents. For example:

class ViewUserActivity : AppCompatActivity() {

    companion object {

        const val KEY_USER = "user"

        fun intent(context: Context, user: User): Intent {
            val intent = Intent(context, ViewUserActivity::class.java)
            intent.putExtra(KEY_USER, user)
            return intent
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_cooking)
        
        val user = intent.getParcelableExtra<User>(KEY_USER)
        //...
    }
}

The call to create this Intent looks and feels familiar to what you would see in Java:

val intent = ViewUserActivity.intent(context, user)
startActivity(intent)

This pattern is great, since it reduces the likelihood that an Intent or Fragment would be missing the needed data to display a user or whatever content it is meant to display. Companion objects are a way to keep some form of static access within Kotlin, and should be used accordingly.

Global Constants

Kotlin allows you to define constants that span across an entire app in one place (if applicable). Typically, constants should have their scope reduced as much as possible, but when scope needs to be global this is a great way to do so without having to go through a constants class.

package com.savvyapps.example

import android.support.annotation.StringDef

// Note that this is not a class, or an object
const val PRESENTATION_MODE_PRESENTING = "presenting"
const val PRESENTATION_MODE_EDITING = "editing"

These can be used as constants anywhere in your project:

import com.savvyapps.example.PRESENTATION_MODE_EDITING

val currentPresentationMode = PRESENTATION_MODE_EDITING

Keep in mind that constants should be kept to as small a scope as possible to reduce complexity. If you have a value that only relates to the user class, put the value there in a companion object instead.

Optional Parameters

Optional parameters make method calls more flexible without having to pass null or a default value. This is especially useful when defining animations.

For example, if you want to have a method defined for fading out views throughout your app, but only in special circumstances do you need to specify the duration, you can define your method like so:

fun View.fadeOut(duration: Long = 500): ViewPropertyAnimator {
    return animate()
            .alpha(0.0f)
            .setDuration(duration)
}
icon.fadeOut() // fade out with default time (500)
icon.fadeOut(1000) // fade out with custom time

Intermediate Kotlin Tips for Android

Extensions

Extensions are useful because they allow you to add to the functionality of a class without having to inherit from it. For example, have you ever wished Activity had some method, such as hideKeyboard()? With extensions, you can accomplish this easily:

fun Activity.hideKeyboard(): Boolean {
    val view = currentFocus
    view?.let {
        val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) 
                as InputMethodManager
        return inputMethodManager.hideSoftInputFromWindow(view.windowToken,
                InputMethodManager.HIDE_NOT_ALWAYS)
    }
    return false
}

With extensions you can easily eliminate the need to have utility classes or methods, and can really improve the readability of your code. We like to take this a step further, and use extensions to improve the organization of our code as well. For example, imagine that you have a basic model, such as an article. This article is probably treated as a data class, which has been fetched from a source such as an API:

class Article(val title: String, val numberOfViews: Int, val topic: String)

Say that you want to determine the relevance of an Article to a user based on some formula. Should you put this directly in the Article class? Some would say that model should only hold the data from the API, nothing more. In this case, extensions can once again work great for you:

// In another Kotlin file, possibly named ArticleLogic.kt or something similar
fun Article.isArticleRelevant(user: User): Boolean {
    return user.favoriteTopics.contains(topic)
}

At the present, this is a simple check to see if the user has the topic of the Article in a list of their favorite topics. But, down the line, the logic might change to where you want to check other attributes of the user as well. Since this logic is held somewhat independently from the Article model, you can change it and feel confident about the purpose of the method and its ability to be changed.

lateinit

A major feature of Kotlin is its dedication to null safety. lateinit provides an easy way to both have null safety and initialize a variable the way Android needs you to. This is a great language feature, but nonetheless takes some getting used to after doing extensive Java development. One of the ideas is that a field must be assigned right away, or declared with the possibility of being null:

var total = 0 // declared right away, no possibility of null
var toolbar: Toolbar? = null // could be a toolbar, could be null

This language feature can be frustrating when dealing with Android layouts. This is because we know that the views are going to exist within the Activity or Fragment, but we cannot declare them right away since it must be done in onCreate/onCreateView after the layout is inflated. You could deal with this by asserting checks in each place you touch the view throughout the Activity, but this would be frustrating to deal with and unnecessary from a null check point of view. Instead, you can use the lateinit modifier:

lateinit var toolbar: Toolbar

Now, it is up to you as the developer to not reference toolbar until it has actually been initialized. This works great when used in conjunction with a library like Butter Knife:

@BindView(R.id.toolbar) lateinit var toolbar: Toolbar

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ButterKnife.bind(this)
        // you can now reference toolbar with no problems!
        toolbar.setTitle("Hello There")
}
Safe Typecasting

Certain Android conventions require safe typecasting because normal typecasting would cause an exception. For example, a typical way of creating a Fragment in an Activity is to first check and see if it exists already, using the FragmentManager. If it does not, you'd create it and add it to the Activity. When first looking at typecasting in Kotlin, you might implement this like so:

var feedFragment: FeedFragment? = supportFragmentManager
    .findFragmentByTag(TAG_FEED_FRAGMENT) as FeedFragment

This would actually cause a crash. When you call 'as', it attempts a cast on the object, which in this case, would be null and would cause a null pointer exception. You need to instead call 'as?', meaning something along the lines of “cast the object and return null if the cast fails." Altogether, the proper initialization of a Fragment would appear like:

var feedFragment: FeedFragment? = supportFragmentManager
    .findFragmentByTag(TAG_FEED_FRAGMENT) as? FeedFragment
if (feedFragment == null) {
    feedFragment = FeedFragment.newInstance()
    supportFragmentManager.beginTransaction()
            .replace(R.id.root_fragment, feedFragment, TAG_FEED_FRAGMENT)
            .commit()
}
Leveraging let

let permits you to execute a block if the value of the object is not null. This allows you to avoid null checks and makes code more readable. In Java this looks like:

if (currentUser != null) {
    text.setText(currentUser.name)
}

Whereas is Kotlin it becomes:

user?.let {
    println(it.name)
}

In this case, along with being more reader friendly, let automatically assigns user to an un-nullable variable, it, which we can continue to use without fear of it being nulled out.

isNullOrEmpty | isNullOrBlank

We need to validate many times throughout developing an Android app. If you've dealt with this without using Kotlin, you may have discovered the TextUtils class in Android. The TextUtils class looks like:

if (TextUtils.isEmpty(name)) {
    // alert the user!
}

In this example, you would realize that a user could set their username to just white spaces and it would pass validation. isNullOrEmpty and isNullOrBlank are built into the Kotlin language and eliminate the need for TextUtils.isEmpty(someString), and provide the additional benefit of checking for just whitespace. You can use the two whenever appropriate:

// If we do not care about the possibility of only spaces...
if (number.isNullOrEmpty()) {
    // alert the user to fill in their number!
}

// when we need to block the user from inputting only spaces
if (name.isNullOrBlank()) {
    // alert the user to fill in their name!
}

Validation of fields is something that is common during a sign-up process in an app. These built-in methods are great for validating fields and alerting the user if something doesn't look right. You could even leverage extension methods to make some custom validation, such as for email addresses:

fun TextInputLayout.isValidForEmail(): Boolean {
    val input = editText?.text.toString()
    if (input.isNullOrBlank()) {
        error = resources.getString(R.string.required)
        return false
    } else if (emailPattern.matcher(input).matches()) {
        error = resources.getString(R.string.invalid_email)
        return false
    } else {
        error = null
        return true
    }
}

Advanced Kotlin Tips for Android

Avoiding Single Abstract Methods for Kotlin Classes

This tip allows you to use lambdas, which allows cleaner, more succinct code. For example, when working in Java, it is very typical to have a simple listener class, such as:

public interface OnClickListener {
    
    void onClick(View v);
}

A great feature of Kotlin is that it performs SAM (Single Abstract Method) conversions for Java classes. A click listener in Java that appears like:

 textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // do something
    }
});

can be reduced in Kotlin to:

textView.setOnClickListener { view ->
    // do something
}

But, the same cannot be done for SAM interfaces created in Kotlin. This is by design but can be a bit surprising and frustrating for newer Kotlin users. If the same interface were defined in Kotlin, the listener would have to look more like:

view.setOnClickListener(object : OnClickListener {
    override fun onClick(v: View?) {
        // do things
    }
})

To reduce this, you would instead want to write out your listeners in the class like so:

private var onClickListener: ((View) -> Unit)? = null

fun setOnClickListener(listener: (view: View) -> Unit) {
    onClickListener = listener
}

// later, to invoke
onClickListener?.invoke(this)

Which would bring you back to the simpler lambda syntax that automatic SAM conversion makes possible.

Coroutines Instead of AsyncTask

Because AsyncTask is clunky and tends to cause leaks, we prefer to use coroutines as they improve readability and don't leak memory. Check out this resource to learn the basics of coroutines. Note that since coroutines are experimental in Kotlin 1.1, we still recommend RxJava for most async purposes.

At this time, coroutines require an additional dependency:

com pile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.13"

and in the gradle.properties file:

kotlin.coroutines=enable

Using coroutines, we can write simple, inline asynchronous code, and still modify the UI in a direct way:

fun bindUser() = launch(UI) {
   // Call to API or some other things that takes time
    val user = Api.getUser().execute()
    // continue doing things with the ui
    text.text = user.name
}

Concluding Note

We compiled these tips from what we considered the most useful things we've learned since we started developing in Kotlin. A special thanks to my colleague Nathan Hillyer for his contributions. Our hope is that knowing these tips will give you a headstart in using Kotlin for your Android projects. You can also check out Kotlin's documentation. Jake Wharton, an Android developer for Square, also offers a few useful resources, including his presentation and his notes on the viability of using Kotlin for Android.

Expect more tips as Kotlin continues to evolve as a programming language. Have a favorite Kotlin tip? Share it with us on our contact form or on Twitter @savvyapps.

John is an Android developer who loves open-source software, Material Design, and cats. Not always in that order.

You made it this far so...