Skip to content

Instantly share code, notes, and snippets.

@AmrSaleh
Last active June 10, 2019 10:57
Show Gist options
  • Save AmrSaleh/a474e76d431c27b814347aa40275c2a0 to your computer and use it in GitHub Desktop.
Save AmrSaleh/a474e76d431c27b814347aa40275c2a0 to your computer and use it in GitHub Desktop.
We highlight some of the tips and tricks to take into consideration while migration your Android Java project to Kotlin.

Android Java to Kotlin (Things to take into consideration)

Kotlin-for-Android-development

1. View Binding

The old school way

Usually while writing view binding code for android you need to declare your view components and then bind them to their respective views using findViewById which is a lot of hassle and can even reduce your performance if done in a wrong way. Typically is tedious and looks something like this.

class MainActivity extends Activity {
  TextView title;
  TextView subtitle;
  TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      
      title = (TextView) findViewById(R.id.title);
      subtitle = (TextView) findViewById(R.id.subtitle);
      footer = (TextView) findViewById(R.id.footer);
  }
}

Now imagine you have a large Activity and a lot of views that need to be bound, this way turns into a big mess very quickly!

The Butter Knife way

So, in order to improve this we used the Butter Knife which is a library that helps us do field and method binding for Android views a lot easier and we no longer need to write the findViewById ourselves but we still need to declare the views and add appropriate annotations like so:

class ExampleActivity extends Activity {
  @BindView(R.id.title) TextView title;
  @BindView(R.id.subtitle) TextView subtitle;
  @BindView(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
  }
}

Although, this looks cleaner it is still a lot of hassle and Kotlin has the right answer for this.

The Kotlin way

We will tackle this using the amazing Kotlin Android Extensions plugin which was developed by the Kotlin team to make Android development easier. The Kotlin Android Extensions plugin allows us to obtain the same experience we have with some of these binding libraries like Butter Knife but, without having to add any extra code or annotations. Just import the layout and start modifing the views directly without needing to declare them or bind them.

// Using R.layout.activity_main from the 'main' source set
import kotlinx.android.synthetic.main.activity_main.*

class MyActivity : Activity() {
  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      
      // Instead of findViewById<TextView>(R.id.title)
      title.setText("Hello, world!")
  }
}

See how this is much cleaner and hassle free. One note is to pay attention to your views ids in the layouts now because they will be an important part of our classes.

The steps to include and use this Kotlin extension can be found in great details at the Kotlin Extension documentation page.

2. Static functions

Usually when writing Java you sometimes need to write static function. Maybe you have a utilities class or maybe you have a static function that starts your activity. But how can we convert those static functions since Kotlin has no static modifier. well let's hear what they say about it in the docs.

In Kotlin, unlike Java or C#, classes do not have static methods. In most cases, it's recommended to simply use package-level functions instead.

If you need to write a function that can be called without having a class instance but needs access to the internals of a class (for example, a factory method), you can write it as a member of an object declaration inside that class.

Even more specifically, if you declare a companion object inside your class, you'll be able to call its members with the same syntax as calling static methods in Java/C#, using only the class name as a qualifier.

If you are coming from a Java background you probably have the following questions: what are package-level functions and how can they replace static functions?

These are functions and members (yes you can have package level members as well) within a Kotlin file, but not class. Then they belong to whatever package you declare the file belongs to, OR, to the entire root package if you don’t specify one at all. More details and amazing example about package level functions and members at this interesting blog by Alex Dunn.

So How does that relate to us. Well from my experience we usually have three different static functions use cases

  1. Utility functions that do not relate to any class or object.

An example for that is a function that does a simple task like converting pixels to dp or returning my name. The best way to handle such functions in Kotlin is to define them as package-level functions and maybe put a couple of them together in the same .kt file if you feel they are related to each other.

//HelperFunctions.kt
package com.mypackage.helpers
// this is a file with random functions that belong to the package above.
 
fun sayMyName() {
    println("Amr Saleh")
}
 
fun kilogramsToPounds(kiloes: Float){
    return kiloes * 2.2046
}
 
val baseUrl: String = "https://gist.github.com/AmrSaleh/a474e76d431c27b814347aa40275c2a0"
  1. Static functions that act as helpers for a certain class.

Those are static functions that belong in a certain class. For example, we used to make a function called startActivity in an activity class to make an intent and start the activity. This function helped starting the activity easily from an outside class without having to worry about the intent creation.

class HomeActivity extends Activity {
    public static void startActivity(Context context) {
        Intent intent = new Intent(context, HomeActivity.class);
        context.startActivity(intent);
    }
}

Those kinds of static function can be replaced in Koltin using companion object functions like so:

class HomeActivity : Activity {
    companion object {
        fun startActivity(context: Context) {
            val intent = Intent(context, HomeActivity::class.java)
            context.startActivity(intent)
        }
    }
}

And then you can call it like this

HomeActivity.Companion.startActivity(myContext);
  1. Static functions that can be related to certain Data structure.

Sometimes you have static functions that do some operations on a specific data type or structure. For example, a function that checks if a String matches my name. I know that I will always call this function on a String and hence it might be better to implement it in Kotlin as an Extension function to that specific type (String in our case).

fun String.matchesMyName(): Boolean {
    return it == "Amr Saleh"
}

and then you can call it like so

if("Amr Saleh".matchesMyName()) println("Yaay!")

You can place an extension in any of your .kt files and you can access it from other files normally.

Lastly, If you feel you have multiple extension functions related and you would like to put them in the same file then you might want to put them in a separate .kt file as package-level functions.

3. Constants

Constants in Java are usually defined as static final variables and sometimes they are also public, but how can we define those in Kotlin?

By now we know that there is no static modifier in Kotlin and we will probably handle the static part the same way we handled static functions in the previous section.
But what about the final modifier? Well we can map it to either val or const. but what is the difference between vals and consts. According to this handy stack overflow answer.

consts are compile time constants. Meaning that their value has to be assigned during compile time, unlike vals, where it can be done at runtime.

This means, that consts can never be assigned to a function or any class constructor, but only to a String or primitive.

For example:

 const val foo = complexFunctionCall()   //Not okay
 val fooVal = complexFunctionCall()  //Okay
 
 const val bar = "Hello world"           //Also okay

With this knowledge we know that we will usually define our constants with both val and const modifiers since they are normally primitive values or strings that do not change and are usually defined at compile time. Like so:

const val MY_CONSTANT = "Constants"

Now we know how to define constants in Kotlin but, we still don't know where to put them.

  1. Instance constants (final)

If you try to normally define constants in your class among other variables using the const modifier, you will get a compile time error Const 'val' are only allowed on top level or in objects and that actually makes perfect sense because variables inside your class are instance variables and it doesn't make sense for an instance variable that gets created into each instance to be a const. If it is a constant then why are you duplicating it in all your instances. Maybe they are defined at run time and differ from one instance to another but their values should stay the same once they are created. Well in that case they can be defined as usual vals that get initualized once and their value can never be changed. An example for that would be like so:

class MyClass(objectName: String) {
  val OBJECT_NAME: String = objectName
}

And that brings us to the next question. What if it is indeed a constant and is defined at compile time? Then you should define it as a class level constant and that brings us to the second type of constants.

  1. Class level constants (static final -> const val in companion object)

Class level constants are similar to class level functions and we will actually deal with them the same way we delt with static functions. We can simply put our constants in the class companion object like so.

class MyActivity {
  companion object {
      private const val MY_KEY = "my_key"
  }
}

Note: You may have noticed private modifier added to our constant. This can be swapped with public or any other visibility modifier if you would like to access this const from other parts of your code.

  1. Package level constants

If your constants are not related to a certain class or you want to put all your constants in a single place, then you can always use the handy package level constants by defining your contants directly in a Kotlin file and then you can import them and use them in other classes normally.

package com.mypackage.consts

const val MY_CONST1 = "my_const1"

And then you can import and use them like so:

 package com.myotherpackage
  
 // Import the package level constat you want to use 
 import com.mypackage.consts.MY_CONST1
 
 // Package level constant in same package
 const val MY_CONST2 = "my_const2"

 class MyClass {
   var someVariable: String
   var otherVariable: String
   
   init {
     someVariable = MY_CONST1
     otherVariable = MY_CONST2
   }
 }

4. Use Safe operators (as?, ?., ?:, etc.)

One of most important benefits of using Kotlin is Null Safety, but you should not take it for granted and you should always use safe operators in order to get this benefit.

Using code converting feature in your Android Studio or other code editors usually result in not so safe Kotlin code because Java is generally not null safe and turning it into safe Kotlin code requires some decisions from the author.

Generally if you have a variable that can be null or a collection that can hold elements that might be null, you should make use of nullable types like so:

var myPromoCode: String? = null // myPromoCode is a nullable String that can be null at some point
val nullableList: List<Int?> = listOf(1, 2, null, 4) // list of ints or nulls

Having defined your variables properly will help the compiler tell you where you should use safe operators.

Avoid unsafe casting that could result in a Null Pointer Exception for example

(mView as LoginView).onLoginFailed(networkError.message) // Not Safe

Such casting using as will return null and explode your App if mView cannot be cast to LoginView.

So we should always check before doing such cast

if(mView is LoginView)
  (mView as LoginView).onLoginFailed(networkError.message) // Safe but not Elegant

An even better solution is to use the safe casting as? and safe call ?. operators

(mView as? LoginView)?.onLoginFailed(networkError.message) // Safe and Elegant

You can also use the Elvis ?: operator to handle the null case like so

val cannotBeNull: String = nullableString ?: "String is null"

Null safety is one amazing feature of Kotlin that can prevent a lot of crashes. Make sure to read more about it in the documentation.

5. Avoid using the Double Bang (!!)

The double bang or !! operator is a not-null assertion operator (!!) converts any value to a non-null type and throws an exception if the value is null.

If you can't tell from this definition this will cause NULL POINTER EXCEPTION if the value is null and will crash your App. It is like throwing the null safety out the window and deciding to write non safe code.

You should avoid using the !! almost all the time and replace it with ?. and ?: and proper null case handling.

val l: Int = b!!.length         // Not safe
val l: Int = b?.length ?: 0     // Safe
val l: Int? = b?.length         // Safe

6. Use default parameters / arguments

Default arguments in Kotlin can help you reduce your code significantly and avoid unnecessary function overloading. Here is an example: In Java you would do something like that

public String foo(String name, int number, boolean toUpperCase) {
    return (toUpperCase ? name.toUpperCase() : name) + number;
}
public String foo(String name, int number) {
    return foo(name, number, false);
}
public String foo(String name, boolean toUpperCase) {
    return foo(name, 42, toUpperCase);
}
public String foo(String name) {
    return foo(name, 42);
}

you can replace all those overridden functions in one function in Kotlin using default arguments like so:

fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false): String {
  return (if (toUpperCase) name.toUpperCase() else name) + number
}

You can find the previous example and further practice default arguments here.

7. Use Data Classes for your models

Usually in any application we will need to hold the data in some classes which is usually referred to as the Model part of your arcitecture. Those model calsses are usually classes that contain fields and methods for accessing them and at some casses equality checking and hashing. They are typically containers for the data and although they are important they are tedious to create and are genrally not fun to implement.

Well Kotlin has the best answer for that. Data classes

When you use data classes the compiler automatically derives the following members from all properties declared in the primary constructor:

  • equals()/hashCode() pair;
  • toString() of the form "User(name=John, age=42)";
  • componentN() functions corresponding to the properties in their order of declaration;
  • copy() function (see below)

An example for that is that a user model data class would be defined like that:

data class User(var name: String, var age: Int)

And you can directly access properties and use equals and toString funcitions and all the functionality mentioned above.

Now if you wanted to implement this same class in Java you will need to actually implement each getter and setter and the equals and toString and other functions that would be tedious and time consuming.

You can check this Android Pub article for the actual Java code for such class and further details about Data classes.

A note about Data classes: Some might argue that data classes are not that important because any good IDE will be able to generate all the getters and setters and other functions for you. Well Well Data classes are more helpful than any IDE generated functions for the following reasons:

  1. Your generated functions through your IDE are still code that you have to maintain and re-generate whenever any change is introduced in your model or properties. However, you don't need to worry about that when using data classes.
  2. Another thing is that you are always not sure if the code generated by your IDE is the standard code supported by the language or not.
  3. It is not easy to spot if you edited or modified some of that generated code.

So data classes are not just about saving on typing but are actually more than that.

These reasons were mentioned by Hadi Hariri the VP of developer advocacy at Jetbrains at the Introduction to Kotlin (Google I/O '17).

Conclusion

Migrating your android code to Kotlin is easy and you can benefit a lot from the automatic code converting by your IDE but you should never take it for granted. You should always read through your code and make sure to use the best practices to either reduce your code and benefit from all the amazing Kotlin benefits and achieving the sought after null safety.

Lastly, I would like to thank you all for taking the time to read through this article and I hope it helped you make your Kotlin migration process easier and more fruitful.

If you have any comments or additions please feel free to reach out to me.

Amr Saleh
Software Engineer
GitHub / LinkedIn

Sources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment