User:DBrant (WMF)/Kotlin

Getting serious about Kotlin
This is a collection of guidelines and best practices for development using Kotlin and its various affordances, specifically as it relates to building clean and performant Android apps.

General thoughts
As we all know, the Android platform gives us a hundred different ways of doing the same thing, so we have to be very mindful and careful about picking a single way and remaining consistent throughout our code.

View bindings
Currently our code uses several distinct ways to bind views at runtime:


 * ButterKnife, which creates binding classes at compile time and requires us to hook into them at runtime.
 * Plain old findViewById in certain cases.
 * Kotlin synthetics, which are convenient for our purposes, but are now deprecated!

So then, the new One True Way of binding views seems to be the View Binding library of the Jetpack suite. Let's work towards updating all of our layouts to use these bindings, and remove the usage of ButterKnife and synthetics. This will have the benefit of using fewer dependencies, and probably fewer total methods and cleaner code.

Use @Parcelize when passing objects between activities
There are many cases where we have to exchange information between activities. In the cases where the information is a complex object, we usually resort to serializing it to JSON, and then deserializing it from JSON in the destination activity. This is a pretty expensive operation.

Android provides the "Parcelable" interface which is supposed to solve this issue, but the problem is that it requires a lot of boilerplate code to make a class be Parcelable. Fortunately Kotlin now offers the Parcelize annotation which supposedly auto-generates all the necessary boilerplate.

Let's investigate the Parcelize annotation and use it whenever we transfer complex objects between activities.

Use kotlinx.serialization to serialize objects to and from the server
The new kotlinx.serialization library seems to be much more powerful and more appropriate for our purposes than Gson, so let's work towards adopting it. A huge benefit is that this library is reflectionless, which should make it much more efficient. It also has nice conveniences like a  annotation.

Use lateinit liberally
If you have an Activity that contains fields that are initialized in onCreate, make sure they are defined as. In other words, avoid using nullable types as much as possible.

Use 'by lazy' sparingly
The  annotation allows you to lazily initialize a field, but beware: this comes at a nonzero cost. The  annotation itself will become an object that will "contain" the lazy initialization code and run it in a thread-safe way when it's first requested. Therefore only objects that are truly heavy and expensive should be initialized this way.

Data classes
When possible, model classes (e.g. objects that are deserialized from server responses, objects exchanged between activities, etc.) should be declared as, to be maximally clear and concise.

Use java.time everywhere
The  and related classes have been deprecated for a while, and have been superseded by. We should adopt it everywhere we use dates, time ranges, etc.

Things to check after conversion
The automatic converter offered by Android Studio that converts Java files to Kotlin is good, but not perfect. After you convert a file, check the following things for possible improvement or optimization:

If-not-null-then constructs
Very often we might have a Java construct like this:

if (object != null) { object.method; }

When converted to Kotlin, this can become:

object?.method;

Usages of TextUtils.isEmpty
We have numerous usages of TextUtils.isEmpty which checks whether a string is empty or null. In Kotlin this is easily replaceable by:

[string].isNullOrEmpty

Other string operations
We have other usages of TextUtils and StringUtils functions that are now part of the Kotlin standard library. For example, string joins can now be done by:

[string array].joinToString(",")

Chained safe-call operators with null-check
Beware of constructs like this:

parentObject?.childObject?.childChildObject!!.method

In the above line, if either  or   is null, it will cause a crash. This is because the !! operator will operate on any value that comes out of the chain, even if it's null! (i.e. the execution will not "bail out" if something in the chain leading up to the !! operator happens to be null.)

Instead, always prefer using safe-call operators the whole way through, and if this is impossible, try using a let construct:

parentObject?.childObject?.let { it.childChildObject!!.method }

Use @JvmOverloads carefully
The  annotation makes code a little bit cleaner, but it can actually cause issues when creating custom Views and using   to overload the constructors. This is because the View constructors (usually three of them; with one, two, and three parameters) don't always call through to each other in the same way that  assumes. This can result in styling issues in your custom View.

The truly proper solution is to keep three separate constructors, making each constructor call super with the same parameter signature, and avoid using  for custom Views.

String split preserves empty strings
The string split function returns empty substrings as valid items. So for example, if you have a string, then   will return the list.

More importantly, if you have an empty string, splitting it by any delimiter will return a non-empty list with one item in it, which is. Kotlin does not seem to provide a way to suppress empty strings when splitting. The proper way to do this would be something like this:

string.split(",").filter { it.isNotEmpty }