Kotlin Tips and Tricks for Efficient Programming [Part-2]

Abhishek Ippakayal
Simform Engineering
6 min readMar 28, 2024

Leverage Kotlin language features to enhance code quality, cleanliness, and readability.

Hello, everyone! Here I am with my new blog Kotlin Tips and Tricks Part 2. If you haven’t read the first part yet, here is the link:

In the first blog, I explored Kotlin’s cool features that make daily coding life easy with its clean and concise code. So read it once and don’t forget to appreciate it by giving claps.

In part 2, we will explore some of the other cool features of Kotlin that make it clean and concise.

Coroutines

A coroutine is an instance of computation that is suspendable. It can be conceptualized as a lightweight thread, but it is cheaper than threads. Coroutines are used to write asynchronous code in the same way we write synchronous code. Unlike using callback methods for asynchronous code, writing asynchronous code with coroutine is mostly the same as writing normal code that executes sequentially. Let’s see how:

In the above example, performRequest is a function that simulates the API call, which can block the main thread.

runBlocking and launch are coroutine builders, which indicate that the compiler should start a coroutine. There is one other coroutine builder that is async .

suspend is a keyword that marks a function as suspendable, which means this function can suspend its work and resume later. Suspendable functions are only called by other suspendable functions or coroutine builders.

Here, the code is written the same as the usual code that we write: sequential and clean.

Let us understand the code:

  1. runBlocking starts a coroutine and ensures the main thread waits until all child coroutines are completed.
  2. performRequest method suspends the current coroutine and frees the underlying thread so that other tasks can be performed until the performRequest task is completed. While suspending the coroutine, all the variables and state of the coroutine are saved so that when the coroutine resumes, the execution can be started from where it left off.
  3. The coroutine resumes when the performRequest is completed and the second print line is executed.
  4. launch creates a new coroutine and starts its execution concurrently with the parent coroutine. That is why the last line println(parentResponse) is printed first.
  5. A newly started child coroutine prints the first line and moves to the second line. performRequest suspends the coroutine until the specified delay and then resumes and prints the response and does the same thing with the second performRequest,after the println(response2) program ends.

The sequence of the output can vary as coroutines are executed concurrently and factors like delays and dependencies influence the exact output sequence.

Benefits of using coroutine:

  1. Writing asynchronous code with a coroutine is easy to understand, clean, concise, and readable.
  2. Structured concurrency means the parent-child relationship. This ensures the parent coroutine will only end after all the child coroutines are completed, or canceling the parent coroutine will cancel all the child coroutines. This limits the lifetime of all child coroutines, making handling memory leaks and error propagation easier.
  3. Coroutines are lightweight and cheaper than threads. Multiple coroutines can be executed on a single thread without performance issues.

In this blog, coroutines are explained at a basic level. There is much more than this, like dispatchers, await, join, etc. See official Kotlin docs for a detailed understanding of coroutines.

Sealed classes

A sealed class provides a mechanism to restrict the direct subclasses of a class. It helps to define the hierarchy of a class by declaring all subclasses in the same file.

It is marked with a sealed keyword before class keyword. All subclasses of a sealed class must be declared in the same file. Extending a sealed class outside of the file leads to a compile-time error.

Sealed classes are conceptually the same as enums where types are predefined and no new type can be added later. However, subclasses of sealed classes can have multiple instances, and all instances can have their state, which is not true in the case of an enum.

Let’s understand a use case of the sealed class:

While creating a library, a base and possible subclasses are defined for the input data. The library can handle only these subclasses. However, if the code from which the library is accessed tries to create a subclass of any of the input classes and pass it to the library as input, the library will not handle this accurately and create an inconsistent state.

In this case, sealed classes are the perfect choice where we can declare all the possible subclasses in the same file at compile time so whenever any part of the code tries to create a subclass, the compiler shows an error.

Used with the when expression, all possible cases of a sealed class are handled because the compiler already knows all possible types. And if a type is missed in the when expression, the compiler will show an error to handle that case.

When possible types are pre-defined, sealed classes are perfect to limit the hierarchy. Also, having pre-defined types makes the code more predictable.

Singleton class

A singleton class is a class with a single instance of it that is created at the application level and shared within the whole application code. This saves the memory and improves the performance.

In Kotlin, to create a singleton, the object keyword is used instead of a class. This creates an object at the global level and can be accessed within the whole application code.

object keyword makes sure that only one instance is created at the global level and initialization of the object is thread-safe, so it guarantees only one instance that is accessible at the global level. Also, the object keyword initializes lazily, so initialization happens when the first time this instance is accessed.

Here’s the code that initiates the singleton class using an object:

In short, creating a singleton class is concise and clean in Kotlin.

Referential equality (===)

Kotlin, besides having an == operator, which compares the values of two variables, has an === operator, which compares the reference of the variables.

This operator compares the memory address of the two variables and returns true if both objects are pointing to the same memory address.
For primitive data types like Int, Float Boolean, etc., this works the same as == .

Also, there is !== for checking not referentially equal objects.
Below is an example of it.

Destructuring declarations

Destructuring declarations are used to create multiple variables at once. This involves destructuring an object into individual variables.

Kotlin uses componentN() methods for destructuring. For each variable of an object that is being destructured, a componentN() method starting from 1 to N is used to extract values. For example, if an object has two properties: name and age, component1() extracts name value and component2() extracts age value.

This destructring only works if the object has componentN() methods.

Kotlin data classes, by default, creates these methods for us. But for other custom objects, it is not implemented by default. So for custom classes, if we want to use restructuring, we need to manually create componentN() methods for the attributes of an object.

Lazy

Lazy in Kotlin is a delegated property that can be used to initialize a property only when it is accessed the first time.

It will compute the value of a property only once and provide the same value whenever that property is accessed.

It executes the initialization lambda in a thread-safe manner so concurrent access to the property will not lead to multiple initializations.

Here is an example of a lazy delegated property in Kotlin.

Benefits of using lazy

  1. Improved performance: If the computation of such a variable is expensive, then using lazy will optimize the code's performance, as the value is computed only when it is first accessed, not when the class is initialized. If the variable is used conditionally, then it will improve the performance by not computing the value of the variable if it is not used.
  2. Efficiency: The lazy delegate computes the value only when the variable is first accessed and then returns the same value in subsequent access. This saves memory and processing by eliminating multiple initializations.

Note: lazy delegate property is only used with val type variables not with var .

Conclusion

Kotlin’s powerful features, such as coroutines, sealed classes, singleton objects, referential equality, destructuring declarations, and lazy initialization, provide developers with a rich toolset to write clean, concise, and efficient code. By leveraging these features effectively, we can enhance code quality, readability, and performance, making our programming experience more enjoyable and productive.

As you continue exploring Kotlin, keep experimenting with these features and sharing your knowledge with the community to foster innovation in the Kotlin ecosystem!

For more updates on the latest development trends, follow the Simform Engineering blog.

Follow Us: Twitter | LinkedIn

--

--