In this three-part series, we’ve taken an in-depth look at Kotlin, a modern programming language for Android apps that runs in the Java Virtual Machine (JVM).
While Kotlin is far from the only alternative programming language that’s designed to run on the JVM, it does have a lot to offer Android developers. So if you’ve recently been feeling frustrated with the Java part of Android development, then you'll want to consider giving Kotlin a go.
In the first two instalments, we covered the following:
Java vs. Kotlin: Should You Be Using Kotlin for Android Development? In this introductory article, we looked at what Kotlin has to offer Android developers, as well as some potential drawbacks you need to be aware of before deciding whether Kotlin is right for you.
Coding Functional Android Apps in Kotlin: Getting Started. In this post, we looked at how to configure Android Studio to support Kotlin code, created an Android app written entirely in Kotlin, familiarised ourselves with Kotlin's basic syntax, and even saw how switching to Kotlin can mean you never have to write another
findViewById
again.
Now that you’re in a position where you can replace the Java portions of your projects with Kotlin code that has the same end result, let’s move on and look at some areas where Kotlin has the advantage over Java.
In this final installment, we’re going to explore some of the more advanced Kotlin features that allow you to perform tasks that would either be much more verbose in Java, or not achievable with Java alone.
By the end of this article, you’ll know how to use Kotlin to trim a ton of boilerplate code from your projects, extend existing classes with new functionality, and make those frustrating NullPointerException
s a thing of the past.
No More Nulls
How many times have you run into a NullPointerException
while testing your code? Today, it’s pretty clear that allowing developers to assign null to an object reference wasn’t the best design choice! Attempting to use an object reference that has a null value is a huge source of bugs across many different programming languages, including Java. In fact, NullPointerException
has been such a huge source of annoyance that Java 8 added the @NonNull
annotation specifically to try and fix this flaw in its type system.
The problem lies in the fact that Java allows you to set any variable of a reference type to null—but if at any point you try to use a reference that points to null, then the variable won’t know where to look because the object doesn’t actually exist. At this point, the JVM can’t continue the normal path of execution—your application will crash, and you’ll encounter a NullPointerException
. Trying to call a method on a null reference or attempting to access a field of a null reference will both trigger a NullPointerException
.
If you’ve experienced the pain of NullPointerException
s (and haven’t we all at some point?) then there’s good news: null safety is integrated into the Kotlin language. The Kotlin compiler doesn’t allow you to assign a null value to an object reference, as it specifically checks your code for the presence of possible null objects during compilation. If the Kotlin compiler detects that a NullPointerException
may be thrown at runtime, then your code will fail at compile-time.
Kotlin’s null-safe design means you’ll (almost) never encounter null situations and NullPointerException
s originating from Kotlin code. The only exception is if you trigger a NullPointerException
by incorrectly using one of Kotlin’s special operators, or if you use one of these operators to deliberately trigger a NullPointerException
(we’ll explore operators in more detail later in this article).
Null Safety in Practice
In Kotlin, all variables are considered non-nullable by default, so attempting to assign a null value to a variable will result in a compilation error. For example, the following won’t compile:
var example: String = null
If you do want a variable to accept a null value, then you’ll need to explicitly mark that variable as nullable. This is the exact opposite of Java, where every object is considered nullable by default, which is a huge part of why NullPointerException
s are so common in Java.
If you do want to explicitly declare that a variable can accept a null value, then you’ll need to append a ?
to the variable type. For example, the following will compile:
var example: String? = null
When the compiler sees this kind of declaration, it recognises that this is a nullable variable and will treat it a bit differently, most notably preventing you from calling a method or accessing a property on this nullable reference, once again helping you avoid those pesky NullPointerExceptions. For example, the following won’t compile:
var example : String? = null example.size
Although Kotlin’s design means that it’s difficult to encounter NullPointerExceptions originating from Kotlin code, if your project features a mixture of Kotlin and Java then the Java portions of your project can still be a source of NullPointerExceptions.
If you’re working with a blend of Kotlin and Java files (or perhaps you specifically need to introduce null values into your Kotlin code) then Kotlin includes a range of special operations that are designed to help you gracefully handle any null values you do encounter.
1. The Safe Call Operator
The Safe Call operator ?.
gives you a way of dealing with references that might potentially contain a null value, while ensuring that any call to this reference won’t result in a NullPointerException
.
When you add a Safe Call operator to a reference, that reference will be tested for a null value. If the value is null, then null is returned, otherwise the object reference will be used as you originally intended. While you could achieve the same effect using an if
statement, the Safe Call operator allows you to perform the same work in much less code.
For example, the following will return a.size
only if a
is not null—otherwise, it’ll return null:
a?.size
You can also chain Safe Call operators together:
val number = person?.address?.street?.number
If any of the expressions in this series is null, then the result will be null, otherwise the result will be the requested value.
2. The Elvis Operator
Sometimes you’ll have an expression that could potentially contain a null value, but you don’t want to throw a NullPointerException
even if this value does turn out to be null.
In these situations, you can use Kotlin’s Elvis operator ?:
to provide an alternate value that will be used whenever the value turns out to be null, which is also a good way of preventing the propagation of null values in your code.
Let’s look at an example of the Elvis operator in action:
fun setName(name: String?) { username = name ?: "N/A"
Here, if the value to the left of the Elvis operator is not null then the value of the left-hand side is returned (name
). But if the value to the left of the Elvis operator is null then the expression on the right will be returned, which in this instance is N/A
.
3. The !! Operator
If you ever want to force your Kotlin code to throw a Java-style NullPointerException, then you can use the !!
operator. For example:
val number = firstName!!.length
Here, we’re using the !!
operator to assert that the firstName
variable is not null. As long as firstName
contains a valid reference, then the variable number is set to the length
of the string. If firstName
doesn’t contain a valid reference, then Kotlin will throw a NullPointerException
.
Lambda Expressions
A lambda expression represents an anonymous function. Lambdas are a great way of reducing the amount of code needed to perform some tasks that come up all the time in Android developmment—for example, writing listeners and callbacks.
Java 8 has introduced native lambda expressions, and these are now supported in Android Nougat. Although this feature isn’t something that’s unique to Kotlin, it’s still worth taking a look at them. Plus, if you’re working on a project that contains both Kotlin and Java code, then you can now use lambda expressions across your entire project!
A lambda expression comprises a set of parameters, a lambda operator (->
) and a function body, arranged in the following format:
{ x: Int, y: Int -> x + y }
When constructing lambda expressions in Kotlin, you need to bear the following rules in mind:
The lambda expression should be surrounded by curly braces.
If the expression contains any parameters, then you need to declare them before the
->
arrow symbol.If you’re working with multiple parameters, then you should separate them with commas.
The body goes after the
->
sign.
The main benefit of lambda expressions is that they allow you to define anonymous functions and then pass these functions immediately on as an expression. This allows you to perform many common development tasks more succinctly than you could in Java 7 and earlier, as you no longer need to write the specification of the function in an abstract class or interface. In fact, the lack of lambdas in Java 7 and earlier is a big part of why writing listeners and callbacks has traditionally been so clunky in Android.
Let’s look at a common example: adding a click listener to a button. In Java 7 and earlier, this would typically require the following code:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(this, "Button clicked", Toast.LENGTH_LONG).show(); } });
However, Kotlin lambda functions allow you to set a click listener using a single line of code:
button.setOnClickListener({ view -> toast("Button clicked") })
Already, this is much more succinct and easier to read, but we can go further—if a function takes another function as the last parameter, then you can pass it outside of the parentheses list:
button.setOnClickListener() { toast("Button clicked") }
And if the function only has one parameter that’s a function, you can remove the parentheses completely:
button.setOnClickListener { toast("Button clicked") }
Extension Functions
Similar to C#, Kotlin allows you to add new functionality to existing classes that you wouldn’t otherwise be able to modify. So if you think a class is missing a useful method then why not add it yourself, via an extension function?
You create an extension function by prefixing the name of the class you want to extend to the name of the function you’re creating.
For example:
fun AppCompatActivity.toast(msg: String) { Toast.makeText(this, msg, Toast.LENGTH_LONG).show() }
Note that the this
keyword inside the extension function corresponds to the AppCompatActivity
instance which .toast
is called on.
In this example, you just need to import the AppComptActivity
and Toast
classes into your .kt file and then you’re ready to call the .
notation on instances of the extended class.
When it comes to Android development, you may find extension functions particularly useful for giving ViewGroups
the ability to inflate themselves, for example:
fun ViewGroup.inflate( @LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View { return LayoutInflater .from(context) .inflate(layoutRes, this, attachToRoot) }
So instead of having to write the following:
val view = LayoutInflater .from(parent) .inflate(R.layout.activity_main, parent, false)
You can simply use your extension function:
val view = parent.inflate(R.layout.activity_main)
Singleton Objects
In Java, creating singletons has typically been pretty verbose, requiring you to create a class with a private constructor and then create that instance as a private attribute.
The end result is usually something like this:
public class Singleton { private static Singleton singleton = new Singleton( ); private Singleton() { } public static Singleton getInstance( ) { return singleton; }
Rather than declaring a class, Kotlin allows you to define a single object, which is semantically the same as a singleton, in one line of code:
object KotlinSingleton {}
You can then use this singleton right away, for example:
object KotlinSingleton { fun myFunction() { [...] } } KotlinSingleton.myFunction()
Conclusion
In this article, we took an in-depth look at Kotlin’s null-safe design, and saw how you can ensure your Android projects remain null safe even when they contain a mix of Java and Kotlin code by using a range of special operators. We also took a look at how you can use lambda expressions to simplify some common development tasks in Android, and how to add new functionality to existing classes.
Combined with what we learnt in Part 1: Java vs. Kotlin and Part 2: Getting Started, you now have everything you need to start building effective Android apps using the Kotlin programming language.
Have fun with the Kotlin language, and why not check out some of our other courses and tutorials on Android programming?
Comments