Mastering Kotlin
Mastering Kotlin
Learn the tricks that will let you take the most out
of the language
Antonio Leiva
This book is for sale at http://leanpub.com/mastering-kotlin
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean
Publishing process. Lean Publishing is the act of publishing an in-progress ebook
using lightweight tools and many iterations to get reader feedback, pivot until you
have the right book and build traction once you do.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
5 Java interoperability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Package-level functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Extension functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Function overloads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Instance and static fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
CONTENTS
Data classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Sealed classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Inline functions and reified types . . . . . . . . . . . . . . . . . . . . . . . . . 43
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Introduction
Several years ago I discovered Android by chance. I was no longer enjoying the kind
of software I developed at that time and was in search of new adventures.
Android fascinated me since the very beginning: in a few hours, you could have
an App running on your mobile and, with little skills, create your first test screens.
Everything could be done from your home and with free software.
But there was something in Android development that I didn’t quite like much: the
language. At work, I was using C#, a language that has always been at the forefront
regarding features, and with Java, I was jumping several steps backward.
Concepts such as properties, null treatment or immutability were already present in
most languages, but not in Java. Also, in Android we have the added problem that
most devices need to compile using an old version of Java, so we couldn’t even take
advantage of the small language improvements.
That’s why, for me, Kotlin was a turning point. The day I decided to try Kotlin, it
was love at first sight. A world of possibilities appeared right in front of my eyes,
giving me back things that I lost when I switched from C# to Java, but with other
new features that made my code much cleaner and more flexible.
But when you learn a new language, not everything is a path of roses. Writing the
same code with the new language is easy, but there are new concepts that you have
to master and don’t know when to use. You need a change of mentality to solve the
same problems differently.
For me, that was an essential point in my transition, and that is why since then I
dedicated myself to helping other Android developers to follow the same road.
That was 2015, and since then I’ve led thousands of developers and their companies
to boost their Android productivity by switching to Kotlin, in many different ways:
hundreds of articles, talks in conferences, the first Kotlin book ever¹, and extremely
practical online course², live training, 1 to 1 mentoring³, etc.
¹https://antonioleiva.com/kotlin-android-developers-book/
²https://antonioleiva.com/online-course/
³https://antonioleiva.com/mentoring
Introduction 2
So if you think I can help you or your company, please don’t hesitate to write me to
[email protected]⁴.
In this ebook, I’ll show you some other ways to take the most out of the language.
Kotlin is super-powerful, and you will see here that there are little things in Android
that you can’t do with Kotlin.
So relax and enjoy what I’m bringing for you.
⁴mailto:[email protected]
1 Anko layouts on Android
If you’re already developing Android Apps using Kotlin, you’ve probably heard
about Anko layouts and been thinking about using them or at least considered taking
a look at them.
The truth is that Anko⁵ has been around for really long. In fact, when I started writing
my book about Kotlin on Android⁶ at the beginning of 2015, this library already
existed.
But in case you hadn’t heard about it, Anko is a library developed by the Kotlin
team with the goal of simplifying the interaction with the Android framework.
Its most outstanding feature is Anko layouts, which is the main topic of this first
guide. But it also has other features, such as small DSL to execute asynchronous
tasks⁷ in a very simple way, another one to build easy dialogs and alerts⁸, a set of
functions to deal with SQLite⁹, and even an implementation of coroutines¹⁰, among
many other things.
I must admit that I’m a huge fan of this library because it has a lot of impressive
features and it’s been beneficial to me to understand Kotlin and how to apply it to
Android development.
But there’s something in this library that always pushed me back: Anko layouts
DSL.
I’ve been developing Android Apps for like 6 years, and I’m really fluent with XML
already. So when I tried to do something with Anko layouts, it was like starting from
scratch to me.
But for simple examples, it looked really powerful! I could write a small login form
in a breeze. With just this code:
1 UI(true) {
2 verticalLayout {
3 val user = editText {
4 hint = "Username"
5 }
6 val pass = editText {
7 hint = "Password"
8 }
9 button("Login") {
10 setOnClickListener {
11 longToast("User: ${user.text}, Pass: ${pass.text}")
12 }
13 }
14 }
15 }
• It’s ** straightforward to start with it**: the DSL is really intuitive for simple
examples, and the code you need to write is clean and nice to read.
¹¹https://github.com/antoniolg/Bandhook-Kotlin/
1 Anko layouts on Android 6
• It skips the inflation step, so the views written with Anko are much faster
to initialize that those written in XML. There are some articles like this from
Simon Vergauwen¹² that show it’s even 4 times faster.
• Using Kotlin code means you can do more complex things: for instance, set
a dimension that is the sum of two dimensions, ** more straightforward data
binding, views are more natural to reuse and compose**…
• You need to start learning from scratch: new API, new rules. So, at the
beginning, you’ll find some things much more difficult to do that just by using
XML. This shouldn’t be a stopper though.
• Some things are inherently more complex, like styling your views, setting layout
params.
• You need to write your views blindly. You don’t have a powerful designer
to implement the view, or at least see the view you’re writing. That’s half-
true because there’s a plugin that should show a preview, but I’ve never seen it
working, it’s broken quite often. You cannot use tools: attributes either, which
are very useful when doing layouts.
• Some things may be even impossible. There are some XML properties that don’t
have their equivalent in Java, so you can’t do much here.
• In general, I’d encourage you to try it and take your own decisions. I know
people that love Anko layouts and can understand why. I can find an excellent
use case for simple layouts like custom views for dialogs or for RecyclerView
adapters.
1 implementation "org.jetbrains.anko:anko-sdk19:$anko_version"
The SDK you use should correspond to the minimum version you support (or
previous). There’s sdk-15, sdk-19, sdk-21, sdk-23 and sdk-25. So let’s say your
minSDK is 17, you should use sdk-15. Then, depending on what other support
libraries you use, you will add the corresponding Anko one: implementation
“org.jetbrains.anko:anko-appcompat-v7:$anko_version”
And there are others for design library, CardView, etc. The complete reference is at
Anko repository¹⁴.
In onCreate, it sets the view from the property as the view of the activity, and also
uses the toolbar from this layout. The generic type of the activity is just an interface
that forces implementers to hold a reference to the toolbar:
¹⁴https://github.com/Kotlin/anko/
1 Anko layouts on Android 8
As you can see, the parent class is a CoordinatorLayout¹⁵, which holds an AppBar-
Layout with a Toolbar, and a [RecyclerView].
An interesting thing is how the layout params are declared. It’s a function where you
set width and height by argument (default is WRAP_CONTENT), and then a block to add
the rest of extra parameters:
¹⁵https://antonioleiva.com/coordinator-layout/
1 Anko layouts on Android 9
1 .lparams(matchParent, matchParent) {
2 behavior = AppBarLayout.ScrollingViewBehavior()
3 }
You can know more about this at the repository wiki¹⁶. ### Applying styles For each
view, there exists a themed version that allows you to apply a theme, such as the
toolbar here:
1 toolbar = themedToolbar(R.style.ThemeOverlay_AppCompat_Dark_ActionBar)
But, if what you want is to apply a style, you cannot use an XML one. Instead, you
need to use the functions apply or applyRecursively. This second one, in case you
want to apply the style also to the sub-views:
As a way to extract styles, I implemented this extension function for the recycler:
¹⁶https://github.com/Kotlin/anko/wiki/Anko-Layouts#is-it-extensible
1 Anko layouts on Android 10
1 fun AutofitRecyclerView.style() {
2 clipToPadding = false
3 columnWidth = dimen(R.dimen.column_width)
4 scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY
5 horizontalPadding = dimen(R.dimen.recycler_spacing)
6 verticalPadding = dip(2)
7 addItemDecoration(PaddingItemDecoration(dip(2)))
8 }
1 recycler = autoFitRecycler()
2 .apply(AutofitRecyclerView::style)
And these are basically the rough edges you need to know to understand the code.
There are some more complex views, which uses an AppBarLayout with a Collaps-
ingToolbarLayout¹⁷, which holds an ImageView, a Toolbar and a TabLayout. Then
the main area is using a ViewPager. I’m leaving the code here for a reference on how
it looks:
1 coordinatorLayout {
2
3 themedAppBarLayout(R.style.ThemeOverlay_AppCompat_Dark_ActionBar) {
4 fitsSystemWindows = true
5
6 collapsingToolbarLayout = collapsingToolbarLayout {
7 fitsSystemWindows = true
8 collapsedTitleGravity = Gravity.TOP
9 expandedTitleMarginBottom = dip(60)
10
11 image = squareImageView {
12 fitsSystemWindows = true
13 }.lparamsC(matchParent) {
14 collapseMode = COLLAPSE_MODE_PARALLAX
15 }
16
17 toolbar = toolbar {
18 popupTheme = R.style.ThemeOverlay_AppCompat_Light
19 titleMarginTop = dip(16)
¹⁷https://antonioleiva.com/collapsing-toolbar-layout/
1 Anko layouts on Android 11
• You can use a language you’re more familiarized with, so it’s easier to start
doing more complicated things. I had never done anything on buildSrc folder,
¹⁹https://blog.gradle.org/kotlin-meets-gradle
²⁰https://github.com/gradle/kotlin-dsl/releases/
²¹https://github.com/antoniolg/Bandhook-Kotlin/
2 Kotlin DSL to write Gradle scripts on Android 13
and it was quite easy for me to create my own class and use it in the rest of the
script files.
• The IDE helps you a lot more: nice autocomplete, the errors are detected
by the compiler, imports added automatically… All you know and love from
your regular Kotlin code is kind of extrapolated here.
1 applicationId = Config.Android.applicationId
2 minSdkVersion(Config.Android.minSdkVersion)
3 targetSdkVersion(Config.Android.targetSdkVersion)
4 versionCode = Config.Android.versionCode
5 versionName = Config.Android.versionName
That said, things are evolving really fast in this aspect: the final version of Kotlin
Gradle Script is close and new Canary versions of Android Studio simplify things a
lot. So I’m sure this alternative will be a real option in the close future, and lots of
companies will start using it.
²²https://antonioleiva.com/free-kotlin-android-course/
²³https://antonioleiva.com/classes-kotlin/
2 Kotlin DSL to write Gradle scripts on Android 14
1 distributionBase=GRADLE_USER_HOME
2 distributionPath=wrapper/dists
3 zipStoreBase=GRADLE_USER_HOME
4 zipStorePath=wrapper/dists
5 distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-all.zip
With this, you’ll get a better idea of the things that are not working. You can do it
now if you want, but it will obviously fail.
²⁴https://github.com/gradle/gradle/releases
²⁵https://github.com/antoniolg/android-kotlin-gradle-script
2 Kotlin DSL to write Gradle scripts on Android 15
1 ext {
2 // Android config
3 androidBuildToolsVersion = "27.0.3"
4 ...
5 }
You can have that in your root Gradle file, and then use it in your modules:
1 buildToolsVersion parent.ext.androidBuildToolsVersion
That’s pretty easy, and works fine. The alternative in Kotlin DSL is this: for each
variable you need to create an extra like this:
As you can imagine, this doesn’t scale up very well. Having all this code for each
variable is a pain.
So the alternative I found is to create a configuration file in the buildSrc, and add
all you need in an object that you can then instantiate in any Gradle files. If you
don’t know about it, the buildSrc is basically a place where you put all the code that
you want to use when building the project scripts. You can find more info in Gradle
Docs²⁶.
To configure it, just create this folder structure under the buildSrc folder:
²⁶https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources
2 Kotlin DSL to write Gradle scripts on Android 16
Forget about .gradle and build folders and create the rest. Under that folder, also
create a new build.gradle.kts with this content:
1 plugins {
2 `kotlin-dsl`
3 }
The Config file will be the one to hold the variables you may use in your project.
You can use whatever structure you want. While looking for info on how to do this,
I found the repository from Arturo Gutiérrez²⁷ that uses this structure, and I liked
it. I’ve moved to use objects instead of classes though, which makes more sense here:
1 object Config {
2 object BuildPlugins
3 object Android
4 object Libs
5 object TestLibs
6 }
Then, each child object has its own values. For instance, the Android one:
²⁷https://github.com/arturogutierrez/gradle-script-kotlin-example/blob/master/buildSrc/src/main/kotlin/
ProjectConfiguration.kt
2 Kotlin DSL to write Gradle scripts on Android 17
1 object Android {
2 val buildToolsVersion = "27.0.3"
3 val minSdkVersion = 19
4 val targetSdkVersion = 27
5 val compileSdkVersion = 27
6 val applicationId = "com.antonioleiva.bandhookkotlin"
7 val versionCode = 1
8 val versionName = "0.1"
9 }
You can also use some top variables to make it easier to edit:
1 defaultConfig {
2 applicationId = Config.Android.applicationId
3 minSdkVersion(Config.Android.minSdkVersion)
4 targetSdkVersion(Config.Android.targetSdkVersion)
5 versionCode = Config.Android.versionCode
6 versionName = Config.Android.versionName
7
8 testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
9 }
building the project, and interpreting the output. As a reference, I can leave you some
parts of the Gradle files here (and you can, of course, check the complete project on
Github²⁸). For the root build.gradle:
1 buildscript {
2 repositories {
3 jcenter()
4 google()
5 }
6 dependencies {
7 classpath(Config.BuildPlugins.androidGradle)
8 classpath(Config.BuildPlugins.kotlinGradlePlugin)
9 }
10 }
11
12 allprojects {
13 repositories {
14 jcenter()
15 google()
16 }
17 }
This one becomes quite simple, as we’ve extracted all kinds of configuration to the
Config.kt file. The module file is a bit more complicated. For the plugins, you do it
like this:
1 plugins {
2 id("com.android.application")
3 kotlin("android")
4 kotlin("kapt")
5 }
For regular plugins, you just use the function id, and Kotlin plugins use the function
kotlin. Then the Android section is like you saw above. You need to try to discover
whether it’s a function or a property. Check the repository the most typical ones.
Then, for the build types:
²⁸https://github.com/antoniolg/Bandhook-Kotlin/
2 Kotlin DSL to write Gradle scripts on Android 19
1 buildTypes {
2 getByName("release") {
3 isMinifyEnabled = false
4 proguardFiles("proguard-rules.pro")
5 }
6 }
You can’t just create a release block, but instead, it’s required to find it by name,
and then you can configure it. The dependencies are easy, just functions where you
set the name of the dependency:
1 dependencies {
2 compile(Config.Libs.kotlin_std)
3 ...
4 }
Coroutines are based on the idea of suspending functions: functions that can stop the
execution when they are called and make it continue once it has finished running
their background task.
Suspending functions are marked with the reserved word suspend, and can only be
called inside other suspending functions or a coroutine.
Thus, you cannot call a suspending function everywhere. There needs to be a
surrounding function that builds the coroutine and provides the required context.
Something like this:
Are coroutines threads then? Not exactly. They work similarly, but are much more
lightweight and efficient. You can have millions of coroutines running on a few
threads, which opens a world of possibilities.
There are three ways you can make use of the coroutines feature:
• Low-level implementations: Kotlin provides a set of libraries that you can find
in kotlinx.coroutines³¹ repository, which solves some of the hardest parts and
provides a specific implementation for different scenarios. There is one for
Android³², for instance.
• Higher-level implementations: if you want to have a solution that provides
everything you need to start using coroutines right away, there are several
libraries out there that do all the hard work for you, and the list keeps growing.
I am going to stick to Anko, which provides a solution that works well on
Android, and you are already familiar with the library.
1 async(UI) {
2 ...
3 }
1 async(UI) {
2 val r1: Deferred<Result> = bg { fetchResult1() }
3 val r2: Deferred<Result> = bg { fetchResult2() }
4 updateUI(r1.await(), r2.await())
5 }
bg returns a Deferred object, which suspends the coroutine when the function
await() is called, just until it returns the result. We will see this option in the example
below.
³¹https://github.com/Kotlin/kotlinx.coroutines
³²https://github.com/Kotlin/kotlinx.coroutines/tree/master/ui/kotlinx-coroutines-android
3 A first walk into Kotlin coroutines on Android 24
As you know, as Kotlin compiler can infer the type of the variables, this could be
simpler:
1 async(UI) {
2 val r1 = bg { fetchResult1() }
3 val r2 = bg { fetchResult2() }
4 updateUI(r1.await(), r2.await())
5 }
The second alternative is to make use of the integration with listeners that is provided
on specific sub-libraries, depending on which listener you are going to use. For
instance, on anko-sdk15-coroutines, there exists an onClick listener whose lambda
is indeed a coroutine. So you can start using suspending functions right away inside
the listener block:
1 textView.onClick {
2 val r1 = bg { fetchResult1() }
3 val r2 = bg { fetchResult2() }
4 updateUI(r1.await(), r2.await())
5 }
As you can see, the result is very similar to the previous one. You are just saving
some code.
To use it, you will need to add some of these dependencies, depending on the listeners
you want to use:
1 compile "org.jetbrains.anko:anko-sdk15-coroutines:$anko_version"
2 compile "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"
3 compile "org.jetbrains.anko:anko-design-coroutines:$anko_version"
app/build.gradle
1 compile "org.jetbrains.anko:anko-coroutines:$anko_version"
Next, if you remember, I told you that you need to opt in for the feature. Otherwise,
it will show a warning. To do that, simply add to the modulebuild.gradle:
app/build.gradle
1 kotlin {
2 experimental {
3 coroutines "enable"
4 }
5 }
Now you are ready to start using coroutines. Let’s go first to the detail activity.
You can change this code:
1 doAsync {
2 val result = RequestDayForecastCommand(intent.getLongExtra(ID, -1)).execute()
3 uiThread { bindForecast(result) }
4 }
This reference allows calling the activity when it is available and cancels the
coroutine in case the activity has been killed. Be careful to ensure that all calls to
activity methods or properties are done via this ref object.
But this can get a little complicated if the coroutine interacts several times with the
activity. In MainActivity, for instance, this solution becomes a little more convoluted:
You cannot use ref() inside the bg block because the code inside that block is not a
suspension context, so you need to save the zipCode into another local variable.
I honestly think that leaking the activity for 1-2 seconds is not that bad, and probably
will not be worth the boilerplate. In fact, we were already leaking it with the previous
solution. So if you can ensure that your background process is not taking forever (for
instance, by setting a timeout to your server requests), you are safe by not using
asReference().
ui/activities/MainActivity.kt
So with all this, you now have your asynchronous code written synchronously very
easily. As I said at the beginning, this code is quite simple, but imagine convoluted
cases where the result of one background operation is used by the next one, or when
you need to iterate over a list and execute a request per item. All this can be written
as regular synchronous code, which is much easier to read and maintain.
Bye bye, callback hell. You can check the exact changes in this branch from the book
sample App³⁵.
³⁵https://github.com/antoniolg/Kotlin-for-Android-Developers/tree/chapter-27
4 Kotlin Android Extensions
Kotlin Android Extensions is another plugin that the Kotlin team has developed
to make Android development simpler. The plugin automatically creates a set of
properties that give direct access to all the views in the XML. This way we do not
need to explicitly find all the views in the layout before starting using them.
The names of the properties are taken from the ids of the views, so we must be careful
when choosing those names because they now become a relevant part of our base
code. The plugin also infers the type of these properties from the XML, so there is no
need to do any extra castings.
Kotlin Android Extensions is that it does not require adding libraries to our project.
The plugin generates the code it needs to work only when it is required.
How does it work under the hood? These properties delegate to functions that request
the view, and a caching function that prevents from doing a findViewById every time
a property is used. Be aware that this caching mechanism only works if the receiver
is an Activity or a Fragment. It skips the cache if it is inside an extension function
because the plugin is not able to add the necessary code.
app/build.gradle
1 apply plugin: 'com.android.application'
2 apply plugin: 'kotlin-android'
3 apply plugin: 'kotlin-android-extensions'
1 <FrameLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent">
5
6 <TextView
7 android:id="@+id/welcomeMessage"
8 android:layout_width="wrap_content"
9 android:layout_height="wrap_content"
10 android:layout_gravity="center"
11 android:text="Hello World!"/>
12
13 </FrameLayout>
As you can see, the TextView has welcomeMessage id. In the MainActivity you now
could write:
To use it, you require a special import (the one I write below), but the IDE can write
the import for you:
4 Kotlin Android Extensions 30
1 import kotlinx.android.synthetic.main.activity_main.*
The new Android Studio activity templates now include nested layouts, by using the
include tag. It is important to know that you must add a synthetic import for each
XML you use:
1 import kotlinx.android.synthetic.main.activity_main.*
2 import kotlinx.android.synthetic.main.content_main.*
As I mentioned above, the generated code includes a view cache. So if you ask for
the view again, this does not require another findViewById. Let’s see what it is doing
behind the scenes.
1 ...
2 public View _$_findCachedViewById(int var1) {
3 if(this._$_findViewCache == null) {
4 this._$_findViewCache = new HashMap();
5 }
6
7 View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
8 if(var2 == null) {
9 var2 = this.findViewById(var1);
10 this._$_findViewCache.put(Integer.valueOf(var1), var2);
11 }
12
13 return var2;
14 }
15
16 public void _$_clearFindViewByIdCache() {
17 if(this._$_findViewCache != null) {
18 this._$_findViewCache.clear();
19 }
20
21 }
Here it is the view cache we were talking about. When a view is requested, it tries to
find it in the cache. If it is not there, it uses findViewById and adds it to the cache.
Pretty simple indeed.
Besides, it adds a function to clear the cache: clearFindViewByIdCache(). You can
use it for instance if you rebuild the view, as the old views are not valid anymore.
Then this line:
1 ((TextView)this._$_findCachedViewById(id.welcomeMessage))
2 .setText((CharSequence)"Hello Kotlin!");
So the properties are not real, the plugin is not generating a property per view. It
replaces the code during compilation to access the view cache, cast it to the proper
type and call the method.
4 Kotlin Android Extensions 32
In onViewCreated, I change the text of the TextView. What about the generated
bytecode? Everything is the same as in the activity, with this slight difference:
1 <merge xmlns:android="http://schemas.android.com/apk/res/android"
2 android:orientation="vertical"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent">
5
6 <ImageView
7 android:id="@+id/itemImage"
8 android:layout_width="match_parent"
9 android:layout_height="200dp"/>
10
11 <TextView
12 android:id="@+id/itemTitle"
13 android:layout_width="match_parent"
14 android:layout_height="wrap_content"/>
15
16 </merge>
At the end of the book, I will tell you more about these annotations and how to use
them to improve the interoperability with Java code.
In the example above, I am modifying the text of itemTitle. The generated code
should be trying to find the view from the cache. It does not make sense to copy all
the same decompiled code again, but you can see this in the line that modifies the
text:
4 Kotlin Android Extensions 34
1 ((TextView)this._$_findCachedViewById(id.itemTitle))
2 .setText((CharSequence)"Hello Kotlin!");
The plugin also helps you fill the import. Check that this one is different:
1 import kotlinx.android.synthetic.main.view_item.view.*
• In compilation time, you can reference any views from any other views. This
means you could be referencing a view that is not a direct child of that one. Of
course, this crashes in execution time when it tries to recover a view that does
not exist.
• In this case, views are not cached, as opposed to Activities and Fragments.
Why is this? Here the plugin does not have a place to generate the required code for
the cache. If you again review the code that is generated by the plugin when calling
a property from a view, you will see this:
1 ((TextView)itemView.findViewById(id.itemTitle)).setText((CharSequence)"My Text");
As you can see, there is no call to a cache. Be careful if your view is complex and
you are using this in an adapter. It might impact the performance.
Alternatively, if you are using Kotlin 1.1.4 or newer, you have another option.
4 Kotlin Android Extensions 35
1 androidExtensions {
2 experimental = true
3 }
This experimental flag means that the API is not final, so it can change in the future.
The containerView is the one that we are overriding from the LayoutContainer
interface, and that is all you need. From now on, you can access the views directly,
no need of prepending itemView to get access to the subviews.
Again, if you check the code generation, you will see that it is taking the view from
the cache:
4 Kotlin Android Extensions 36
1 ((TextView)this._$_findCachedViewById(id.itemTitle))
2 .setText((CharSequence)"Hello Kotlin!");
I have used it here on a ViewHolder, but you can see this is generic enough to be used
in any classes.
1 @Parcelize
2 class Model(val title: String, val amount: Int) : Parcelable
Then, as you may know, you can add the object to an intent:
And recover the object from the intent where you need it (in this case, in the target
activity):
1 @ContainerOptions(CacheImplementation.SPARSE_ARRAY)
2 class MainActivity : AppCompatActivity() {
3 ...
4 }
Conclusion
You’ve seen how easy is to deal with Android views in Kotlin. With a simple plugin,
we can forget about all that awful code related to view recovery after inflation. This
plugin will create the required properties for us casted to the right type without any
issues.
Besides, Kotlin 1.1.4 has added some interesting features that will be really helpful
in some cases that were not previously covered by the plugin.
5 Java interoperability
So far, we have been talking about creating an app from scratch. However, you
probably find yourself in a situation where you already have an App written in Java
with thousands of lines of code, and you cannot convert all your code. I will cover
this topic here.
One of the great wonders of Kotlin is that it is entirely interoperable with Java.
Therefore, although all your application code is written Java, you can create a class in
Kotlin and use it from Java without any issues. Calling Kotlin from Java code cannot
be easier. This potentially gives you two advantages:
• You can use Kotlin in a Java project: In any project, you have already started,
you can decide to start writing new code in Kotlin. You can then call it from
Java code.
• If you do not know how to do something in Kotlin, you can write that part in
Java. You may be wondering if there is a case where Kotlin is not enough to do
something on Android. In theory, everything can be done, but the fact is that it
does not matter. If you cannot do it in Kotlin, then implement that part in Java.
Let’s see how this compatibility works, and how Kotlin code looks when used from
Java.
Package-level functions
In Kotlin, functions do not need to be inside a class, but this is not the case in Java.
How can we call a function then? Imagine that we have a file called utils.kt that
looks like this:
5 Java interoperability 39
In Java we can access them through a class that will be called UtilsKt, with some
static methods:
1 UtilsKt.logD("Debug");
2 UtilsKt.logE("Error");
Extension functions
We have been using extension functions a lot throughout this book. However, how
do they look in Java? If you have the following function:
This is applied to a ViewGroup. It receives a layout and inflates it using the parent
view. What would we get if we want to use it in Java? This is the result:
As you can see, the object that applies this function (the receiver) is added as
an argument to the function. Besides, the optional argument becomes mandatory,
because in Java we cannot use default values.
Function overloads
If you want to generate the corresponding overloads in Java, you can use @JvmOver-
loads annotation for that function. In the previous example, you would not need to
specify false for the second argument in Java:
5 Java interoperability 40
1 @JvmOverloads
2 fun ViewGroup.inflate(resId: Int, attachToRoot: Boolean = false): View {
3 return LayoutInflater.from(context).inflate(resId, this, attachToRoot)
4 }
5
6 View v = UtilsKt.inflate(parent, R.layout.view_item);
If you prefer to specify the name of the class when calling Kotlin from Java, you
can use an annotation to modify it. In the utils.kt file, add this above the package
sentence:
1 @file:JvmName("AndroidUtils")
1 AndroidUtils.logD("Debug");
2 AndroidUtils.logE("Error");
3 View v = AndroidUtils.inflate(parent, R.layout.view_item, false);
How does this work in Java? You can simply access the companion object properties
as static fields, by using getters and setters:
1 AppHelper helper = App.instance.getAppHelper();
As a val, it only generates the getter in Java. If it were var, we would also have
a setter. The access to instance has worked automatically because it uses the
lateinit annotation, which also exposes the field that Kotlin uses to store the state.
But imagine we create a constant:
1 companion object {
2 lateinit var instance: App
3 val CONSTANT = 27
4 }
You find that you cannot use it directly. You are forced to access through a Companion
internal class:
1 KotlinClass.Companion.getCONSTANT()
This previous snippet does not look particularly readable. To expose the field in Java
the same way a static field would look, you need a new annotation:
1 @JvmField val CONSTANT = 27
If you have functions in a companion object, they are converted to static methods
using the @JvmStatic annotation. There are several ways to define constants that,
when we use Kotlin from Java, generate different bytecode. If you remember, we
used const val before, and those properties are accessible without using a getter.
Data classes
Some features are clear, but some others are more difficult to know how they may
behave in Java. So let’s take a look at those features that Kotlin has, but Java does
not. One example is data classes. Let’s say we have a data class like this:
5 Java interoperability 42
1 data class MediaItem(val id: Int, val title: String, val url: String)
But are we missing something? First, let’s check if equals works as expected:
Of course, it shows the Toast. The bytecode the class generates has everything it
needs to compare two items and, if the state is the same, then the items are also
the same. However, other things are more difficult to replicate. Remember the copy
feature data classes have? The method is there, but you can only use it passing all
arguments:
So it is not better than just using the constructor. Also, we lose destructuring, as the
Java language does not have a way to express that.
Sealed classes
Another feature that may come to your mind is sealed classes. How do they work
when used from Java? Let’s try it:
5 Java interoperability 43
We have a Filter class that represents a filter that can be applied to items. Of course,
in Java we cannot do:
1 public void filter(Filter filter) {
2 switch (filter) {
3 ...
4 }
5 }
switch in Java only accepts a small number of types, and for Java, sealed classes are
regular classes. So the best you can do is:
1 if (filter instanceof Filter.None) {
2 Log.d(TAG, "Nothing to filter");
3 } else if (filter instanceof Filter.ByType) {
4 Filter.ByType type = (Filter.ByType) filter;
5 Log.d(TAG, "Type is: " + type.getType().toString());
6 } else if (filter instanceof Filter.ByFormat) {
7 Filter.ByFormat format = ((Filter.ByFormat) filter);
8 Log.d(TAG, "Format is: " + format.getFormat());
9 }
So inline functions work, but there is an interesting thing here. When used from
Kotlin, the decompiled code looks like this:
The function is being inlined as expected. What happens when used from Java?
1 ExtensionsKt.toast(this, DetailActivity$$Lambda$0.$instance);
So, though you can use inline functions from Java, they are actually not inlined. It
is calling the function and creating an object for the lambda. That is something to
take into account.
Now, what happens to reified types? This is a function that navigates to the activity
specified in the generic type:
Then, if you try to call this function from Java, you will see that this method appears
to be private, so we cannot use it. Reified functions are not are not available from
Java code.
So now you understand better how all new Kotlin features behave when used from
Java. You see that using the code we write in Kotlin from Java is effortless. Most of
them can still be used, though certainly, we cannot take advantage of some Kotlin
features from Java.
Conclusion
I hope you enjoyed these 5 guides that cover some not so known uses of the language.
As you can see, Kotlin is very flexible, which allows solving the same problems in
different ways.
From now on, I encourage you to try all these guides with practical examples (most
of them include code samples for you to follow) and to keep learning about this
fascinating language.
If you want to do it faster and efficiently, I can help you in many different ways.
The simplest one is the Kotlin book³⁶, where you can learn the language on your own
little by little.
But if you’re really committed to learning the language, I recommend you to take
a look at the online course³⁷, 1 to 1 mentoring³⁸, or I even do live training at
companies. Ask your company, I’m sure they have some budget saved for training
their employees and they can help you get one of these options.
Thanks for reading this guide! If you need something, as usual, you can find me at
[email protected]³⁹.
If you find typos or wrong content in this ebook, please write me and let me know
so that I can fix it.
Best,
Antonio
³⁶https://antonioleiva.com/kotlin-android-developers-book/
³⁷https://antonioleiva.com/online-course/
³⁸https://antonioleiva.com/mentoring
³⁹mailto:[email protected]