Improve Kotlin Code Review Part -1
Here are a few Important features of Kotlin that we can use to improve our coding process.
Here are a few Important features of Kotlin that we can use to improve our coding process.
Use of Unit and Nothing
In Kotlin, Unit
and Nothing
are two different types with distinct purposes.
Unit
is a type with only one value, also called Unit
. It represents the absence of a meaningful value, similar to void
in Java. It is used as the return type of a function that does not return any value, or in other words, a function that only has side effects. For example:
fun printHelloWorld(): Unit {
println("Hello, World!")
}
In the above example, the printHelloWorld
function returns Unit
because it only prints a message to the console, without returning any meaningful value.
On the other hand, Nothing
is a type with no values. It is used to indicate that a function will never return normally. For example, if a function throws an exception, its return type can be declared as Nothing
, because it will never reach its return statement:
fun fail(): Nothing {
throw RuntimeException("Failed")
}
In the above example, the fail
function returns Nothing
because it always throws an exception and never returns normally.
In summary, Unit
is used to represent the absence of a meaningful value, while Nothing
is used to indicate that a function will never return normally.
Destructuring
In Kotlin, you can declare and initialize multiple variables in the same line using destructuring declarations.
Destructuring declarations allow you to break down a data structure (such as a list or a map) into its individual components and assign them to variables in a single step.
Here’s an example of how to use destructuring declarations to initialize multiple variables at once:
val (x, y, z) = listOf(1, 2, 3)
In the above example, we declare three variables x
, y
, and z
, and initialize them with the values from the listOf
function call. The values are assigned to the variables in the same order as they appear in the list.
You can also use destructuring declarations with maps, where you can destructure the key-value pairs into separate variables:
val map = mapOf("name" to "Alice", "age" to 30)
val (name, age) = map
In the above example, we declare two variables name
and age
, and initialize them with the values from the map
variable. The keys "name"
and "age"
are used to destructure the values into separate variables.
Destructuring declarations is a convenient way to initialize multiple variables in a single line of code and can make your code more concise and expressive.
Use typealias
n Kotlin, a type alias is a way to create a new name for an existing type. It does not create a new type, but provides an alternative name for an existing one, which can make your code more readable and expressive.
Here’s an example of a type alias in Kotlin:
typealias UserName = String
In the above example, we create a type alias UserName
for the String
type. This means that wherever UserName
is used, it will be treated as a String
.
We can use this alias in our code to make it more expressive. For example:
fun printUserName(name: UserName) {
println("User name is: $name")
}
val userName: UserName = "Alice"
printUserName(userName)
In the above example, we use the UserName
alias to make the printUserName
function parameter more descriptive. We also use the alias to declare a userName
variable.
Another example of a type alias could be to create a shorter name for a complex type:
typealias IntArray2D = Array<IntArray>
In the above example, we create a type alias IntArray2D
for the Array<IntArray>
type, which represents a two-dimensional array of integers. This can make it easier to work with such an array in our code.
Type aliases are a simple but powerful feature in Kotlin that can make your code more expressive and readable.
Use property delegation to extract common property patterns
An important example is the observable property — a property that does something whenever it is changed. For instance, let’s say that you have a list adapter drawing a list. Whenever data change inside it, we need to redraw changed items. Or you might need to log all changes to a property. Both cases can be implemented using observable from stdlib:
var items: List<Item> by Delegates.observable(listOf()) { _, _, _ ->
notifyDataSetChanged()
}
var key: String? by
Delegates.observable(null) { _, old, new ->
Log.e("key changed from $old to $new")
}
Use Lazy
The lazy
keyword is useful when you have a property that requires expensive computation or initialization and you want to defer that computation until it's actually needed. By using lazy
, you can avoid unnecessary computations and improve the performance of your code.
Here’s an example of how to use lazy
:
val myExpensiveProperty: String by lazy {
// expensive computation or initialization
"Hello, World!"
}
In this example, myExpensiveProperty
is declared as a String
property that is initialized lazily. The lambda passed to lazy
contains the expensive computation or initialization that is only performed when myExpensiveProperty
is accessed for the first time. In this case, the lambda returns the string "Hello, World!"
.
After the first access, the value of myExpensiveProperty
is cached and reused for subsequent accesses. This can help improve the performance of your code by avoiding unnecessary computations.
It’s important to note that lazy
properties are thread-safe by default, which means that the initialization code is executed only once even if multiple threads access the property simultaneously. However, if you need to customize the thread-safety behavior, you can use the LazyThreadSafetyMode
enum to specify the desired behavior.
In summary, the lazy
keyword in Kotlin is used to create lazily initialized properties, which can help improve the performance of your code by deferring expensive computations or initializations until they're actually needed.
Use takeIf
In Kotlin, takeIf
is a standard library function that allows you to perform a conditional operation on an object and return the object if the condition is true, or null
if the condition is false. It has the following signature:
inline fun <T> T.takeIf(predicate: (T) -> Boolean): T?
The takeIf
function takes a lambda expression as its argument, which returns a Boolean
value. If the lambda returns true
for the object on which the function is called, then the object is returned by the takeIf
function. Otherwise, null
is returned.
Here’s an example of how to use takeIf
:
val str: String? = "Hello, World!"
val length = str?.takeIf { it.length > 5 }?.length
In this example, takeIf
is used to check whether the length of the str
string is greater than 5. If it is, then the takeIf
function returns the original string, which is then chained to the length
property to get the length of the string. If the length is not greater than 5, then null
is returned and the length
variable is assigned null
.
Another example could be using takeIf
to filter a list based on a condition, like this:
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumber = numbers.firstOrNull { it % 2 == 0 }?.takeIf { it > 2 }
In this example, takeIf
is used to check whether the first even number in the numbers
list is greater than 2. If it is, then the even number is returned by the takeIf
function. If not, then null
is returned.
Please subscribe and Clap.
Improve Kotlin Code Review — Part II
In the second blog, I will tell you to advance concepts of Kotlin so you can improve code and code review techniques.medium.com