Kotlin tips for your Android app migration

Julien Salvi
Aug 3, 2018 · 13 min read
Image for post
Image for post

Kotlin has become an official language for Android development since the Google I/O 17. Afterwards, a lot of company started their migration from Java to Kotlin (although many did use Kotlin prior to the official support). Here at CINEMUR, the first Kotlin lines of code were added late 2017 when we developed the TV show feature in our application, now 50% of the app is in Kotlin 🙌

After almost a year of learning Kotlin and using it for building my Android applications, I would like to share some useful tips on how to do the migration of an existing Android application. Tips I wish I had received when I first started learning Kotlin for Android.

Table of contents:

  • Get started with Kotlin and Android
  • Null safety in Kotlin
  • let, run, apply, with: the good way
  • The extension functions: your new best friend
  • Goodbye ButterKnife, hello lazy init
  • KTX, Kotlin extensions for Android app development
  • Playing with collections has never been so fun
  • Companion object for constants and static functions
  • The view constructors simplicity

Get started with Kotlin and Android 🚀

Here is a quick introduction on how to set up your project ready for developing with Kotlin:

Inside the buildscript section of your build.gradle, add the Kotlin gradle plugin (the current version is 1.2.51) in order to use Kotlin in your application or library.

buildscript {
ext.kotlin_version = '1.2.51'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

Then in your application build.gralde, add the Kotlin Standard and KTX Libraries. You also need to put the kotlin-android plugin at the top of your file as the following:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 28
buildToolsVersion "28.0.1"
....

}

dependencies {
// Support libs
...
// Kotlin dependencies
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.core:core-ktx:0.3'
}

You are now ready for migrating your Java application in Kotlin. You can quickly convert a Java file to a Kotlin one using this command in Android Studio: Ctrl+Alt+Shift+K. But review carefully the converted code as it may not compile and thus, won’t be optimized.

Null safety in Kotlin 🔒

NullPointerException is probably the most common exception thrown while developing an Android application. Kotlin’s type system is there to eliminate the danger of the null references in our code. Let’s see some examples where a variable may be null.

Here are simple null checks in Java in order to trigger a function or access/set fields from a model:

if (mView != null) {
mView.onMovieLoaded(movie);
}
if (movie !=null && movie.userFeeling != null) {
movie.userFeeling.feeling = RatingEmotions.WANT_TO_SEE;
}
String feeling = RatingEmotions.WANT_TO_SEE;
if (movie !=null && movie.userFeeling != null) {
feeling = movie.userFeeling.feeling;
}

We can reduce this check without an if condition using the ? operator. The function will only be executed if the variable is non null. You can also force the execution of the function with the !! operator but do it at your own risk, be sure your variable is not null while using this operator, otherwise a NullPointerException will be triggered. We can also chain the safe calls in order to access or set a variable. Last but not least, you can use the elvis operator ?: to get a non null value. The default value or expression (set after the elvis operator) will be used if the reference is null.

mView?.onMovieLoaded(movie) // safe call on mView// works but will throw an exception if mView is null
mView!!.onMovieLoaded(movie)
mView.onMovieLoaded(movie) // does not compile if mView is nullable// Chained safe calls to set a variable
movie?.userFeeling?.feeling = RatingEmotions.WANT_TO_SEE
// Use of the elvis operator to get a non null value
val feeling = movie?.userFeeling?.feeling ?: RatingEmotions.WANT_TO_SEE
// Use of the elvis operator to throw an exception
val feeling = movie?.title ?: throw IllegalArgumentException("title should be not null")

let, run, apply, with: the good way 👌

Kotlin introduces a set of standard functions which are very usefull when migrating your Android application. These functions can be called anywhere in your code even on an object. Let us see how to use them in a good way.

  • let: the good guy to ensure safe use

First let’s say you have to add a null check before sending this data through an EventBus. Here is the Java code you could have:

if (video.movie != null) {
EventBus.getDefault().post(new OpenTrailerEvent(video.movie));
}

Now, in Kotlin you can use the let standard function to ensure to get a non null value from the reference and trigger the event. The non null value will be refered as it and send as argument but it could be masked with a more specific name (see below):

// Use of let without masking the result
topVideo.movie?.let {
EventBus.getDefault().post(OpenTrailerEvent(it))
}
// Same function with a more specific name for it
topVideo.movie?.let { movie ->
EventBus.getDefault().post(OpenTrailerEvent(movie))
}

let is especially useful for dealing with nullable var . Using it with val properties (these are the same as final modifier in Java) will be irrelevant.

  • apply and run: perfect for setting properties and triggering methods

The apply function may be very useful when settings properties of a data model or a DialogFragment. It becomes quite nice when calling the methods or properties in the lambda without calling the reference each time. Let’s have a look at the example below where we want to show a DialogFragment in an Activity in Java:

EpisodeRatingDialogFragment dialogFragment = EpisodeRatingDialogFragment.newInstance(episode, userRating);
dialogFragment.setCancelable(true);
dialogFragment.setCallback(this);
dialogFragment.show(getSupportFragmentManager(), TAG);
// Set a new Celebrity object with an id and a name
Celebrity celebrity = new Celebrity();
celebrity.id = someId;
celebrity.name = someName;

In Kotlin, you can take the advantage of the apply function like this to show your dialog. You can achieve the exact same thing using the run function.

// Display episode rating dialog
EpisodeRatingDialogFragment.newInstance(episode, userRating).apply {
isCancelable
= true
setCallback(this@TvShowActivity)
show(supportFragmentManager, TAG)
}
// Set a new Celebrity object with an id and a name
val celebrity = Celebrity().apply {
id
= someId
name = someName
}
  • with: beware the nullability

The with function can be very similar to the run function but it has a very different syntax and you cannot safe call the function in case of a null variable. You will use the safe call operator on this? inside the lamdba when you will call functions or properties with argument.

Let’s see the difference between the two function where we want to setup a RecyclerView. First the using the with function and then run:

// RecyclerView setup using with
with(inTheatersContainer) {
this
?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
this?.setHasFixedSize(true)
this?.isNestedScrollingEnabled = false
this?.addItemDecoration(inTheatersDivider)
}
// RecyclerView setup using run
inTheatersContainer?.run {
this.layoutManager
= LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
this.setHasFixedSize(true)
this.isNestedScrollingEnabled = false
this.addItemDecoration(inTheatersDivider)
}

In conclusion, prefer using run or apply to chain function calls or multiple set of attributes with a null variable.

If you want to know more about the standard functions, have a look at this great tech post: Mastering Kotlin standard functions: run, with, let, also and apply.

The extension functions: your new best friend 🎉

This will save you hundreds of lines of code and will replace most of your utility methods. But do not use them excessively, they are there to make your code more readable with less duplicates. Create a new Kotlin file and start implementating your first extension functions.

Let see a quick example on how to create a simple extension function to check if a device is running Android O or above. The Java static function is place in utility class named UI.

// Check if the device runs Android 0 or above (Java)
public static boolean hasOreo() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
// Same thing but in Kotlin using an extension function
fun hasOreo(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

This extension function can be called from anywhere in your code: it extends the Any class which is the root of the Kotlin class hierarchy. Moreover, with Kotlin you don’t need to use the return keyword to actually return a value. Use the = operator for a simpler and more readable code.

Now let’s see a more complex extension function. For example here is the code to animate the color of an ImageView drawable. In Java you may have this following code:

// Utility method to aniamte the color of a drawable
public static void animateDrawableColor(final ImageView imageView, int colorFrom, int colorTo) {
ValueAnimator valueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
if (imageView.getDrawable() != null) {
DrawableCompat.setTint(imageView.getDrawable(), (int) valueAnimator.getAnimatedValue());
}
}
});
valueAnimator.start();
}
// Let's call this function
UI.animateDrawableColor(imageView, Color.RED, Color.BLUE);

In Kotlin, we are going to create a function extended from the ImageView class. Thanks to the standard functions 6 lines of code is enough to implement this method.

// Extended function to aniamte the color of a drawable
fun ImageView.animateDrawableColor(colorFrom: Int, colorTo: Int) {
with(ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo)) {
addUpdateListener { animator ->
drawable
?.let { DrawableCompat.setTint(it.mutate(), animator.animatedValue as Int) }
}
start()
}
}
// Now you can call it on any ImageView in your code
imageView.animateDrawableColor(Color.RED, Color.BLUE)

Here is another kind of extension function quite useful to check the validity of Retrofit response. We also introduce the inline keyword which means that no function instance will be created when invoking success or error in the example below. inline works well with the lambda parameters but can impact performances on other functions.

fun <T : Any> Response<T>.isValid(): Boolean = isSuccessful && body() != null

inline fun <T: Any> Response<T>.checkValidity(success: (T) -> Unit, error: () -> Unit) {
if (isValid()) {
body()?.let(success)
} else {
error()
}
}

To complete this section, you can now create some extension functions to show some Toast in an Activity or a Fragment, get the current screen height from a Context, convert DP to pixels or inflate a layout from a ViewGroup.

Be smart and unleash the power of the extension functions! :)

Goodbye ButterKnife, hello lazy init 💉

ButterKnife has simplified a lot of boilerplate when developing our Android applications. With Kotlin, this dependency is no longer needed thanks to the lazy delegate function. This combined with an extension function will give you all the resources to bind your views nicely. Let’s see how it looks like.

// View binding inside an Activity with ButterKnife
@BindView(R.id.tvs_header)
TvShowHeaderView mTvShowHeader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tv_show);
ButterKnife.bind(this);
}
@OnClick(R.id.trailer_cover)
public void onPlayTrailer(View v) {
// Open the player
}
@OnTouch(R.id.person_like_button)
public boolean onToggleCelebrityLike(View v, MotionEvent event) {
// Toggle the like
return true;
}

Now, let’s create an extension function from Activity called bind. Here, we are using the lazy property to bind our view. This means that it won’t be computed when the activity will reach onCreate but when the view will be accessed for the first time. Use the lazy delegate when the initialization has an important cost to save CPU cycles.

fun <T : View> Activity.bind(@IdRes idRes: Int): Lazy<T> {
return unsafeLazy { findViewById<T>(idRes) }
}
private fun <T> unsafeLazy(initializer: () -> T) = lazy(LazyThreadSafetyMode.NONE, initializer)

Inside your Activity you can bind your view as the following:

// View binding inside an Activity with a lazy delegate
private val tvShowHeader by bind<TvShowHeaderView>(R.id.tvs_header)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tv_show)
}

So what about the OnClick or OnTouch annotations you could say ? Well, I made the choice to remove them as well. I tooked the advantage of the lambdas which means that you have to set the listener programmatically but it is much nicer than the Java implementation. Now, all OnClick and OnTouch listeners look like this:

// OnClick listener
cover.setOnClickListener { onPlayTrailer(topVideo) }

//
OnTouch listener
buttonLike
.setOnTouchListener { v, event ->
onToggleCelebrityLike(celebrity, v, event)
}

KTX, Kotlin extensions for Android app development 🔧

“The goal of Android KTX is to make Android development with Kotlin more concise, pleasant, and idiomatic by leveraging the features of the language such as extension functions/properties, lambdas, named parameters, and parameter defaults.” — Android KTX GitHub.

Android KTX is a library you will use quite often. Google developed a bunch of extension functions to make your life (or at least the Android development) much more enjoyable. I am going to list some of the KTX functions I used to reduce dozens of lines of code.

// toColorInt(): convert a color string to color int value
val colorInt = "#14141a".toColorInt()
// isVisible: set the visibility to GONE (false) or VISIBLE (true)
movieFeelingCountView.isVisible = movie.ratingCount > 0
// bundleOf(): returns a bundle with the given key/value pairs
fun newInstance(season: Season, newRating: Float) = SeasonRatingDialogFragment().apply {
arguments = bundleOf(KEY_SEASON to season, KEY_RATING to newRating)
}
// parseAsHtml(): parse a string as HTML content
personBio.text = person.biography.parseAsHtml()
// PlusAssign (aka +=): add a view to a ViewGroup
nextTrailersContainer += NextTrailerView(this).apply {
setCallback(this@CinemurPlayerActivity)
setTrailer(movie)
}

To know more about KTX, here is a pretty neat overview of the library made by Joe Birch, Android GDE: Exploring KTX for Android.

Playing with collections has never been so fun 😎

Dealing with collections in Java has always been quite painful before the introduction of the streams in Java 8. You had to write a lot of code to manipulate thems. With Kotlin, it becomes very straight forward and you will be able to achieve a lot in few lines of code.

Quick example to check if a list is empty:

// In Java
boolean isEmpty = actors != null && !actors.isEmpty();
// In Kotlin
val
isEmpty = actors.orEmpty().isNotEmpty()

Here, orEmpty return an empty array even if actors is null.

Now, let’s say that we want to add some views to a container based on a list of movies, but we only want to add movies that has trailers:

// Java code
int size = movies.size();
for (int i = 0; i < size; i++) {
Movie movie = movies.get(i);
if (movie.hasTrailers) {
NextTrailerView trailerView = new NextTrailerView(this);
trailerView.setCallback(this);
trailerView.setTrailer(movie);
nextTrailersContainer.addView(trailerView);
}
}
// Kotlin code
movies.filter { it.hasTrailers }.forEach { movie ->
nextTrailersContainer += NextTrailerView(this).apply {
setCallback(this@CinemurPlayerActivity)
setTrailer(movie)
}
}

Let see the Kotlin code in details. Here, I used the filter and forEach methods to get the same result as the Java code. filter, will return an array where all movies match the predicat set in the lambda. Then, forEach iterates the list in order to add the views to the container with the right data.

Chaining methods is the true power of the collections, it makes the code very clear and more maintainable. You can chain a lot functions like sortedBy, reverse, map, take or drop. If you are familiar to the Java 8 streams you won’t have any troubles.

Let’s play with collection functions and see what’s going on on each step. Let’s take an list of n movies where we want to display the 10 first best action movies.

movies.filter { it.hasGenre(Genre.ACTION) }
.sortedByDescending { it.averageRating }
.take(10)
.mapIndexed { index, movie ->
movie.title.toUpperCase()
movie.explanation = "Best movie number $index"
movie
}
.forEach {
println(it)
}
  • filter: return all action movies of the list.
  • sortedByDescending: sort the filtered list by the movie rating from the best to the lowest-ranked.
  • take : return the first 10 movies of the sorted list. Return the entier list if the size is less than 10 elements.
  • mapIndexed: for each movie of the top 10, it uppercases the title and set a new explanation.
  • forEach : print the 10 best action movies of the initial list.

Companion object for constants and static functions 📝

In every Kotlin class you may add an object declaration marked with the companion keyword. Here, you will put all the private or public constant and static methods you need for your class.

In Java, you may have the following code inside an Activity :

public static final int REQUEST_TVS_COMMENTS = 50;
public static final int REQUEST_DATA_CHANGE = 300;
private static final String EXTRA_TV_SHOW = "tv_show";public static Intent newIntent(Context context, TvShow tvShow) {
return new Intent(context, TvShowActivity.class).putExtra(EXTRA_TV_SHOW, tvShow);
}
// Calling this method in another Activity
startActivity(TvShowActivity.newIntent(this, tvShow));

In your Kotlin class thanks to the companion object you will get the following code:

companion object {
private const val EXTRA_TV_SHOW = "tv_show"

const val REQUEST_TVS_COMMENTS = 50
const val REQUEST_DATA_CHANGE = 300

@JvmStatic fun newIntent(context: Context, tvShow: TvShow): Intent = Intent(context, TvShowActivity::class.java).putExtra(EXTRA_TV_SHOW, tvShow)
}

Now let’s see how to access this method from a Kotlin or a Java class:

// Calling the static method in another Kotlin Activity
startActivity(TvShowActivity.newIntent(this, tvShow)) // Good!
startActivity(TvShowActivity.Companion.newIntent(this, tvShow)) // You can access the companion with the static object Companion
// Calling this method from a Java Activity
startActivity(TvShowActivity.newIntent(this, tvShow)); // Only if the @JvmStatic annotation is specified
startActivity(TvShowActivity.Companion.newIntent(this, tvShow)); // When the @JvmStatic annotation isn't specified

Remember to put the @JvmStatic annotation if your Kotlin static methods are called by a Java class.

One interesting thing is that you can also name your companion object . For example, you can name it Factory if you have some static factory methods in your class.

class CinemurPlayerActivity : GenericCastActivity() {

companion object Factory {
...
}
// Property declarations and initializer blocks
...
// Secondary constructors
...
// Method declarations
...
}

The Kotlin code style recommends to place the companion object at the bottom of your class. I prefer to have a look at all the constants when I open a class. So, place it at the top of your class if you have the same preference.

The view constructors simplicity 👷

Thanks to a simple annotation we’ll be able to write all the constructors for a View in a single line of code.

// TopTrailerView Java class with its constructors
public class TopTrailerView extends ConstraintLayout {

...

public TopTrailerView(Context context) {
this(context, null);
}

public TopTrailerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public TopTrailerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inflate(context, R.layout.view_top_trailer, this);
}
}
// TopTrailerView in Kotlin with its explicit constructors
class TopTrailerView : ConstraintLayout {

constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

constructor(context: Context) : super(context)
init {
inflate(context, R.layout.view_top_trailer, this)
}
}
// The same thing but with the @JvmOverloads annotation
class TopTrailerView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0) :
ConstraintLayout(context, attrs, defStyle) {

init {
inflate(context, R.layout.view_top_trailer, this)
}
}

Adding this simple @JvmOverloads annotation will remove a lot of unnecessary code.

Conclusion

As an official language for the Android app development, Kotlin is now widely used accross the community. I hope this non exhaustive list of tips will help you to migrate or improve your Android applications in Kotlin. Take advantage of this wonderful programming language!

I didn’t mention coroutines, data class, Parcelize or the Kotlin Android Extensions to get rid of the findViewById but you should definilty use them when you start a new Kotlin project.

CINEMUR Engineering

Mobile, Web, Video and Virtual Reality.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store