What I learned by converting a project to Kotlin using Android Studio

Published on July 07, 2017

What I learned by converting a project to Kotlin using Android Studio

Original author: Benjamin Baxter
  • Transfer
To my great joy, I finally got the opportunity to work with the popular Kotlin language - to convert a simple application from Java using the Convert Java File to Kotlin tool from Android Studio. I tried the language and would like to talk about my experience.

I quickly became convinced that this tool converts most of the classes in Java almost flawlessly. But in some places I had to clean up the code for him, and in the process I learned a few new keywords!

Below I will share my observations. Before we begin, I’ll note: if at some point you want to look at what is happening “under the hood”, Android Studio allows you to track all processes; just go to the panel along the following path: Tools → Kotlin → Show Kotlin Bytecode.


Constant long


At first I did not even notice the first change - it was so insignificant. Magically, the converter replaced the constant long in one of the classes with int and converted it back to long with each call. Brr!

companion object {
    private val TIMER_DELAY = 3000
}
//...
handler.postDelayed({
    //...
}, TIMER_DELAY.toLong())

The good news: The constant was still recognized thanks to the keyword val.

The bad news: many processes were accompanied by unnecessary transformations. I expected that type safety in Kotlin would be at a higher level, that everything would be better implemented there. Maybe I overestimated how smart this converter is?

The solution turned out to be simple: you just had to add “L” at the end of the declaration as a variable (something like Java).

companion object {
    private val TIMER_DELAY = 3000L
}
//...
handler.postDelayed({
    //...
}, TIMER_DELAY)

Better late than never


One of the main benefits of Kotlin is null security , which eliminates the risk of null references. This is accomplished using a type system that distinguishes between null and null links. In most cases, null links that are not at risk of running into NPE (Null Pointer Exceptions) are preferable for you. However, in some situations, null references can be useful, for example, when initializing from an onClick () event, such as AsyncTask.

There are several ways to work with null links:

  1. Good old if statements that will check properties for null references before giving access to them (Java should have taught you to them already).
  2. A cool Safe Call Operator (syntax?.), Which checks for zero values ​​in the background for you. If the object is a null reference, then it returns zero (not NPE). No more annoying if!
  3. Forcibly returning NPE using an operator !! .. In this case, you are actually writing Java code familiar and you need to go back to the first step.

Determining which pattern to dwell on to ensure null security is not an easy task, therefore, the converter selects the simplest solution (third) by default, allowing the developer to cope with the problem in an optimal way for his case.

I understood that allowing Kotlin code to throw a null pointer exception is somehow contrary to the advantages that this language gives, and began digging deeper in the hope of finding a solution that would be better than the existing ones.

So I discovered the powerful lateinit keyword. Using lateinit in Kotlin, you can initialize non-zero properties after calling the constructor, which makes it possible to generally move away from zero properties.

This means that I get all the advantages of the second approach without the need to prescribe additional "?.". I simply treat methods as if they were not null in principle, without wasting time on template checks and using the syntax I was used to.

Using lateinit is an easy way to remove operators !!! from the code on Kotlin. If you are interested in other tips on how to get rid of them and make the code more accurate, I recommend David Vávra's post .

Internal and its inner world


Since I was converting from class to class, I was wondering how the already converted classes will interact with those that still remain in Java. I read that Kotlin is perfectly compatible with Java , so, logically, everything should have worked without visible changes.

I had a public method in one fragment that converted to an internal function in Kotlin. In Java, it had no access modifiers , and accordingly it was package private.

public class ErrorFragment extends Fragment {
    void setErrorContent() {
        //...
    }
}

The converter noticed the absence of access modifiers and decided that the method should be visible only within the module / package, using the internal keyword to set the visibility parameters .

class ErrorFragment : Fragment() {
    internal fun setErrorContent() {
        //...
    }
}

What does this new keyword mean? Looking at the decompiled bitcode, we immediately see that the method name from setErrorContent () turned into setErrorContent $ production_sources_for_module_app ().

public final void setErrorContent$production_sources_for_module_app() {
   //...
}

Good news: in other Kotlin classes, it’s enough to know the original name of the method.

mErrorFragment.setErrorContent()

Kotlin will translate it into the generated name itself. If you look at the decompiled code again, you can see how the translation was carried out.

// Accesses the ErrorFragment instance and invokes the actual method
ErrorActivity.access$getMErrorFragment$p(ErrorActivity.this)
    .setErrorContent$production_sources_for_module_app();

Thus, Kotlin deals with changes in names on its own. What about the rest of the classes in Java?

You cannot call the errorFragment.setErrorContent () method from a Java class - after all, this “internal” method does not actually exist (as the name has changed).

The setErrorContent () method is now invisible to classes in Java, as can be seen both in the API and in the Intellisense window in Android Studio. So you have to use the generated (and very bulky) method name.


Although Java and Kotlin usually communicate seamlessly, when calling Kotlin classes from Java classes, you might encounter unforeseen difficulties with the internal keyword. If you plan to upgrade to Kotlin in stages, keep this in mind.

Difficulties with a companion


Kotlin does not allow public static variables and methods that are so typical of Java. Instead, he proposes a concept such as a companion object , which is responsible for the behavior of static objects and interfaces in Java.

If you create a constant in a class in Java and then convert it to Kotlin, the converter does not recognize that the static final variable should be used as a constant, which can lead to interference in the compatibility of Java and Kotlin.

When you need a constant in a Java class, you create a static final variable:

public class DetailsActivity extends Activity {
    public static final String SHARED_ELEMENT_NAME = "hero";
    public static final String MOVIE = "Movie";
    //...
}

As you can see, after conversion, they all ended up in the companion class:

class DetailsActivity : Activity() {
    companion object {
        val SHARED_ELEMENT_NAME = "hero"
        val MOVIE = "Movie"
    }
    //...
}

When other Kotlin classes use them, everything happens as you would expect:

val intent = Intent(context, DetailsActivity::class.java)
intent.putExtra(DetailsActivity.MOVIE, item)


However, since Kotlin, converting a constant, places it in its own companion class, access to such constants from the Java class is not intuitive.

intent.putExtra(DetailsActivity.Companion.getMOVIE(), item)

By decompiling a class in Kotlin, we can notice that the constants have become private and are exposed through the companion wrapper class.

public final class DetailsActivity extends Activity {
   @NotNull
   private static final String SHARED_ELEMENT_NAME = "hero";
   @NotNull
   private static final String MOVIE = "Movie";
   public static final DetailsActivity.Companion Companion = new DetailsActivity.Companion((DefaultConstructorMarker)null);
   //...
   public static final class Companion {
      @NotNull
      public final String getSHARED_ELEMENT_NAME() {
         return DetailsActivity.SHARED_ELEMENT_NAME;
      }
      @NotNull
      public final String getMOVIE() {
         return DetailsActivity.MOVIE;
      }
      private Companion() {
      }
      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

As a result, the code is much more complicated than we would like.

The good news is that we can partially correct the situation and achieve the desired behavior by entering the keyword const in the companion class.

class DetailsActivity : Activity() {
    companion object {
        const val SHARED_ELEMENT_NAME = "hero"
        const val MOVIE = "Movie"
    }
    //...
}

Now, if you look at the decompiled code, we will see our constants! But alas, in the end, we still create an empty companion class.

public final class DetailsActivity extends Activity {
   @NotNull
   public static final String SHARED_ELEMENT_NAME = "hero";
   @NotNull
   public static final String MOVIE = "Movie";
   public static final DetailsActivity.Companion Companion = new DetailsActivity.Companion((DefaultConstructorMarker)null);
   //...
   public static final class Companion {
      private Companion() {
      }
      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

But access from Java classes happens according to the usual scheme!

Please note that this method only works for primitives and strings . To learn more about non-primitives, read the JvmField and Kotlin's hidden costs article .


Cycles and how Kotlin perfects them


By default, Kotlin converts loops in a range with boundaries 0..N-1, which complicates the maintenance of the code, increasing the probability of errors by one .

In my code, for example, there was a nested for loop to add cards to each row - the most common example of a for loop on Android.

for (int i = 0; i < NUM_ROWS; i++) {
    //...
    for (int j = 0; j < NUM_COLS; j++) {
        //...
    }
    //...
}

The conversion took place without much tricks.

for (i in 0..NUM_ROWS - 1) {
    //...
    for (j in 0..NUM_COLS - 1) {
        //...
    }
    //...
}

The resulting code may seem unusual to Java developers - as if it were written in Ruby or Python.

As Dan Lew writes on his blog , Kotlin's range feature is inclusive by default. However, looking at the characteristics of the range at Kotlin, I found them very well designed and flexible. We can simplify the code and make it more readable, taking advantage of the opportunities that they offer.

for (i in 0 until NUM_ROWS) {
    //...
    for (j in 0 until NUM_COLS) {
        //...
    }
    //...
}

The until function makes loops non-inclusive and easier to read. You can finally throw all these ridiculous -1 out of your head!

Useful tips for advanced


For the lazy

It can sometimes be useful to lazily load the member variable. Imagine you have a singleton class that manages a list of data. There is no need to re-create this list every time, so we often turn to a lazy getter . The pattern is obtained in the following spirit:

public static List<Movie> getList() {
    if (list == null) {
        list = createMovies();
    }
    return list;
}

If the converter tries to convert this pattern, the code will not compile, since list is registered as immutable, while createMovies () has a variable return type. The compiler will not allow the return of a mutable object if the method signature specifies an immutable.


This is a very powerful pattern for delegating object loading, so Kotlin enables a special function, lazy , to make loading easier in a lazy way. With its help, the code is compiled.

val list: List<Movie> by lazy {
    createMovies()
}

Since the last line is the returned object, we can now create an object that requires less code to load lazily!

Destructuring

If you had to destructure javascript arrays or objects , then destructuring declarations will seem familiar to you.

In Java, we are constantly creating and moving objects. However, in some cases, we need literally several properties of the object, and it is a pity to time to extract them into variables. If we are talking about a large number of properties, it is easier to access them through a getter. For example, like this:

final Movie movie = (Movie) getActivity()
        .getIntent().getSerializableExtra(DetailsActivity.MOVIE);
// Access properties from getters
mMediaPlayerGlue.setTitle(movie.getTitle());
mMediaPlayerGlue.setArtist(movie.getDescription());
mMediaPlayerGlue.setVideoUrl(movie.getVideoUrl());

Kotlin, however, offers a powerful destructor declaration that simplifies the process of retrieving object properties, reducing the amount of code needed to assign a separate variable to each property.

val (_, title, description, _, _, videoUrl) = activity
        .intent.getSerializableExtra(DetailsActivity.MOVIE) as Movie
// Access properties via variables
mMediaPlayerGlue.setTitle(title)
mMediaPlayerGlue.setArtist(description)
mMediaPlayerGlue.setVideoUrl(videoUrl)


It’s not surprising that in the decompiled code, the methods we refer to getters in the data classes.

Serializable var10000 = this.getActivity().getIntent().getSerializableExtra("Movie");
Movie var5 = (Movie)var10000;
String title = var5.component2();
String description = var5.component3();
String videoUrl = var5.component6();

The converter turned out to be smart enough to simplify the code by destructuring the object. Nevertheless, I would advise reading about lambdas and destructuring . In Java 8, it is common practice to enclose the parameters of a lambda function in brackets if there are more than one, but in Kotlin this can be interpreted as destructuring.

Conclusion


Using the conversion tool in Android Studio was a great first step for me to learn Kotlin. But, after looking through some sections of the resulting code, I was forced to start to delve deeper into this language in order to find more effective ways to write on it.

It’s good that they warned me: after conversion, the code must be subtracted. Otherwise, on Kotlin I would get something unintelligible! Although, frankly, I and Java are not better with this.

If you want to learn other useful information about Kotlin for beginners, I advise you to read this post and watch the video .