We use cookies on this page. View our Privacy Policy for more information.

Bye Java, hello Kotlin alias what do we love about Kotlin

In this article, I would like to share our one-year long experience with Kotlin on Android platform.

Bye Java, hello Kotlin alias what do we love about Kotlin

Introduction 👋

In this article, I would like to share our one-year long experience with Kotlin on Android platform. @SYNETECH, we use Kotlin in all our Android projects we develop. Since our company crafts mobile apps for our customers, we have the responsibility to deliver them in the best quality and stable as quickly as possible. Keep reading if you want to learn about why and how to use Kotlin in your app.

Why do we love Kotlin? ❤️

Since all of us @SYNETECH enjoy coding, we love Kotlin mainly because of its expressivity, null safety, and 100% interoperability with Java code. These things are only the tip of the iceberg. There are many more things in Kotlin you can profit from. But let’s start with these three.

Java code interoperability

The reason why Kotlin was adopted so rapidly by the Android community (and Google itself) is its 100% interoperability with Java. This means that you can use any existing Java code/library in Kotlin. Thanks to this you can use Kotlin and Java code side by side and you don’t have to refactor the whole project when you decide to use Kotlin.

Null-safety checking

 


Source:[The Top 10 Exception Types in Production Java Applications — Based on 1B Events | OverOps Blog]

Kotlin has a null-safety checking feature on syntax level, which increases apps’ stability by checking null references at the build time and not at the run time. You will never have to be afraid of the NPE (NullPointerException) when you use Kotlin properly. The Kotlin uses the very same approach of a definition of “nullable” type as other languages (e.g. Swift) — the type is either optional or required.

Optional type is marked with an extra ? symbol and must be checked if it’s null or not before accessing its value. If the type is required (non-nullable), you don’t have to check it at all. But when you would access optional type directly (without checking) build process would fail.

Expressivity and conciseness

Expressivity of Kotlin is the main reason why we love ❤️ Kotlin so much. Almost all the features mentioned below make the code more readable and so more stable and bug-proof.

Data classes

Do you remember how many lines you had to write to define simple DTOs (Data Transfer Objects) or POJOs (Plain Old Java Objects)? Well, I do. Because of that, Kotlin has its data classes. This data modifier keyword placed before class definition ensures, that all properties defined in its constructor will be part of equals, hashCode , toString and copy methods. You don’t even have to write any getter or setter because, under the hood, Kotlin’s properties are accessed via automatically generated getter and setter methods that you can override. These data classes are useful in uni-directional, stream driven data flows.

) you can easily create different data models for API, domain and so on. You can simply create an extension function that for example converts API entity to the domain model. At the same time, this function ensures, that mapping is going to work even when someone changes the structure of the original model otherwise, the build would fail. // Example of data class (saved more than 10 lines in comparison to Java)
data class User(val name: String, val surname: String, val age: Int)

// Example of custom getters and setters on a property
class MrUser {
   var name: String = ""
       get() {
           return "Mr. $field" // Get name with "Mr." prefix
       }
       set(newValue) {
           println("old: '$field', new: '$newValue'") // Print change
           field = newValue // Store new value
       }
}

More about data classes: Data Classes — Kotlin Programming Language

Sealed classes

Another super-nice modifier for classes is sealed keyword, which limits the hierarchy of a class. When we want to define a closed set of subclasses of a given superclass, we can use this keyword.

Example code defines UIState type that can hold an instance of one of the following types: LoadingState, DataState, ErrorState. sealed class UIState
object LoadingState : UIState() // Data is being loaded
class DataState(val data: Data) : UIState() // Data loaded successfully
class ErrorState(val error: Throwable) : UIState() // In case of failure

) you can handle the change of state quite easily and expressively: fun handleStateChange(newState: UIState) {
   when (newState) {
       is LoadingState -> println("Loading ...")
       is DataState -> println("Got data: ${newState.data}")
       is ErrorState -> println("Error: ${newState.error.message}")
   }
}

More about sealed classes: Sealed Classes — Kotlin Programming Language

Extension functions

As I’ve mentioned above, extensions can be easily used (as the name suggests) for extending an existing class outside its definition, for which you would have to write some kind of Util class.

For example, this piece of code “adds” method makeInvisible() for a class of type View and for its subclasses: fun View.makeInvisible() {
   this.visibility = View.INVISIBLE
}

val button: Button = //...
button.makeInvisible()

More about extension functions: Extensions — Kotlin Programming Language

Single-expression function

Kotlin function can return a value directly without defining its body. So this: fun answerToEverything() = 42

is the same as this: fun answerToEverything(): Int {
   return 42
}

but much shorter and more readable. And even more usable with Kotlin’s statements (e.g.if,when, let, apply, …) that returns value as well. For example: fun parseColor(color: String) = when (color.toLowerCase()) {
   "red" -> "#f00"
   "green" -> "#0f0"
   "blue" -> "#00f"
   else -> "#fff"
}

Default values

Kotlin, unlike Java, allows you to use default values for function parameters, so you don’t have to create multiple functions with different parameter signature.

For example: fun logException(e: Throwable, level = Log.ERROR) {
   // implementation
}

// Then you can call the method without providing the second parameter
logException(IllegalStateException("Test exception"))
logException(IllegalStateException("Test exception"), Log.WARN)

Kotlin also supports default implementation of a function defined in an interface. For example: interface Greeter {
   fun greet() { println("Hi!") }
}

But remember, with great power comes great responsibility.

Scope functions

Kotlin contains few scoping functions. E.g. let, apply,run,with,also. These functions reduce boilerplate code by changing the scope of the caller inside its expression statement.

One example instead of words: private const val DURATION_IN_MS = 3000L

val fadeInAnimation = AlphaAnimation(0f, 1f).apply {
   duration = DURATION_IN_MS
   interpolator = AccelerateDecelerateInterpolator()
   fillAfter = true
}

The code above creates a new instance of AlphaAnimation, on which some properties are set and then the modified animation instance is returned. The magic here is that apply scope function changes this scope to the instance of AlphaAnimation(0f, 1f) and after that, the instance is returned and stored to fadeInAnimation property, ready to be used.

If you want to learn more about scope functions, there is a nice article on this topic Mastering Kotlin standard functions: run, with, let, also and apply.

Property delegates

A delegate is a class that reacts to getting and/or setting the property. So every time when the property is accessed (read from / written to) the delegate is called.

is lazy, which executes its body statement when the property is accessed for the first time and then stores the returned value that will be returned ever after. The example code below computes “answer value”, stores it to its backing field and returns the value when the property is accessed. val answer: Int by lazy {
   computeAnswer()
}

fun computeAnswer() = 42

More about delegates: Delegated Properties — Kotlin Programming Language

Functions & Lambdas

Kotlin is a modern programming language so its syntax supports high-order function and lambda expressions. When you want to write a reactive code, lambdas and function types can be helpful. Declaration of them is simple and readable. Following example presents a method for “getting all users” and has two callbacks. onSuccess callback is supposed to be called when data were successfully retrieved and onError is supposed to be called in case of an error/exception. data class User(val name)

interface UserDataSource {
   fun getAllUsers(
           onSuccess: (users: List) -> Unit,
           onError: (err: Throwable) -> Unit
   )
}

// class UpdateDataSourceImpl: UserDataSource { .... }

UpdateDataSourceImpl().getAllUsers(
   onSuccess = { users -> print(users) }, // here you consume success state
   onError = { err -> print(err.message) } // here you consume error state
)

More about functions & lambdas: Functions: infix, vararg, tailrec — Kotlin Programming Language

Note: There is another way how to define asynchronous functions called coroutines. More at Coroutines Guide — Kotlin Programming Language.

Multiplatform

Another good thing is, that Kotlin is multiplatform! You can reuse your code on multiple platforms. For example, we would like to write business logic once and then reuse it on different platforms — on Android as well as on iOS. You can get more on about multiplatform support at the official site Multiplatform Projects — Kotlin Programming Language.

…and much more

This section covered just a few features of Kotlin, that we really appreciate. However, there is much more you can squeeze out of Kotlin. You can find original Kotlin language reference portal very useful — Reference — Kotlin Programming Language.

Gotchas — What to pay attention to? 🙀

In this section, there are a few examples of things that are good to know about. These things can surprise you, but in a bad way.

Null-checking and Java code

As I mentioned before, Kotlin takes care of checking null references on syntax level (at build time), thanks to that you should not get NullPointerException any more. Sounds nice, but when you access Java code, then there is no information if the field is “nullable” or not. Yes, some code is annotated with @NotNull and @Nullable annotations (Calling Java from Kotlin - Kotlin Programming Language) that Kotlin compiler uses as a source of the nullability information, so be very careful when you access a Java code. It’s better to assume that the property from the Java code is null rather than non-null and therefore not risking the NullPointerException.

Exception handling

In Java there is a throws keyword indicating, that a method is “throwing” a specific exception and then you are supposed to either catch the exception or propagate it up. Otherwise, it would lead to a crash. And since Kotlin doesn’t support checked exceptions Calling Java from Kotlin - Kotlin Programming Language, all exceptions are unchecked, and the compiler does not force you to catch any of them. So, when you call a Java method that can produce an exception, it’s better to check if so and then catch the exception.

Ranges in loops

Kotlin has introduced a new syntax of how to define for-loop. Now you can write a for-loop this way: var max = 10
for (i in 1..max) {
 // ...
}

this: var max = 10;
for (int i = 1 ; i

. So it’s more like this var max = 10
val range = IntRange(1, max)
for (i in range) {
 // ...
}

That means when you would change the value of max it wouldn't have any effect to loop range. It’s not bad that the code is optimized and the range is computed only once, but when you don’t expect this behavior and you would change the value of max property in the body of the loop, then you could spend quite a lot of time with debugging, because you would expect, that the range was changed. Otherwise, you have to use while loop.

Elvis operator precedence

Imagine, you have the following piece of code: val a: Int? = 10
val b: Int = 10
val sum = a ?: 0 + b

You would expect, that sum will be 20, right? But actually, it’s 10! Why? It’s because of the precedence of operators (Grammar), where the addition operator has higher priority than Elvis operator. So Kotlin compiler interprets the expression like this: val sum = a ?: (0 + b)

It’s not obvious, so you have to be careful when you use Elvis operator.

Constructors and Initializers

): class InitOrderDemo(name: String) {
   val firstProperty = "First property: $name".also(::println)

   init {
       println("First initializer block that prints ${name}")
   }

   val secondProperty = "Second property: ${name.length}".also(::println)

   init {
       println("Second initializer block that prints ${name.length}")
   }
}

InitOrderDemo("hello")

Is the following:

First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

So just be careful when you use initializer blocks because it depends on their order.

Conclusion

I loved how it was like to code in Swift back then when Apple introduced Swift. It was so nice to write safe and expressive code. It saved quite a lot of time and final apps were much more stable. So Kotlin adaptation was the best thing that could happen to the Android platform. In cooperation with Android Kotlin library GitHub — Kotlin/anko: Pleasant Android application development, Android development is much faster, easier and less painful.

Don’t be afraid to give a try to Kotlin in your project, it’s really simple to learn it. And if you are afraid of messing up your project, just try Kotlin on Try Kotlin page. Kotlin community together with JetBrains did a lot of awesome work! 👏

What’s your opinion on the Kotlin language? Did you have a good time using Kotlin code in your project? Or was there something about Kotlin that was troubling the process? Let us know in the comments below👇.

Thanks for reading and happy coding 🙂

AppCast

České appky, které dobývají svět

Poslouchejte recepty na úspěšné aplikace. CEO Megumethod Vráťa Zima si povídá s tvůrci českých aplikací o jejich businessu.

Gradient noisy shapeMegumethod CEO Vratislav ZimaMegumethod Scribble