Modern Android development on Kotlin. Part 1

Original author: Mladen Rakonjac
  • Transfer
This article is a translation of an article from Mladen Rakonjac.

It is very difficult to find one project that would cover everything new in Android development in Android Studio 3.0, so I decided to write it. In this article we will analyze the following:



  1. Android Studio 3
  2. Kotlin programming language
  3. Assembly options
  4. Constraintlayout
  5. Data Binding Library
  6. MVVM architecture + repository pattern (with mappers) + Android Manager Wrappers
  7. RxJava2 and how it helps us in architecture
  8. Dagger 2.11, what is dependency injection, why should you use this.
  9. Retrofit (Rx Java2)
  10. Room (Rx Java2)

What will be our application?


Our application will be the simplest one, which covers all of the things listed above: it will have only one function that extracts all googlesamples user repositories from GitHub, saves this data in a local database and shows it to the user.

I will try to explain as many lines of code as possible. You can always see the code that I posted on GitHub .

Android Studio


To install Android Studio 3, go to this page

Android Studio 3 supports Kotlin. Open Create Android Project . There you will see a new flag labeled Include Kotlin support . It is selected by default. Click Next twice and select Empty Activity , then click Finish .

Congratulations! You made the first Android app on Kotlin :)

Kotlin


You can see MainActivity.kt :

package me.fleka.modernandroidapp
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

The .kt extension means that the file is a Kotlin file.

MainActivity: AppCompatActivity () means that we extend AppCompatActivity .

In addition, all methods must have the keyword fun and in Kotlin you do not need to use ; but you can if you want. You should use the override keyword , not annotation, as in Java.

So what does it mean ? in savedInstanceState: Bundle? ? This means that savedInstanceState can be of type Bundle or of type null . Kotlin null is a safe language. If you have:

var a : String

you will get a compilation error because a must be initialized and it cannot be null . This means that you must write:

var a : String = "Init value"

In addition, you will get a compilation error if you do this:

a = null

To make a nullable, you must write:

var a : String?

Why is this an important feature of the Kotlin language? This helps us avoid NPE . Android developers are already tired of NPE. Even the creator of null, Sir Tony Hoar, apologized for the invention. Suppose we have a nullable nameTextView . If the variable is null, then in the following code we get NPE:

nameTextView.setEnabled(true)

But Kotlin, in fact, is good, he will not allow us to do even that. Does it make us use the operator ? or operator !! . Should we use an operator ? :

nameTextView?.setEnabled(true)

The string will be executed only if nameTextView is not null. Otherwise, if you use the operator !! :

nameTextView!!.setEnabled(true)


We will get NPE if nameTextView is null. This is for adventurers :).
This was a small introduction to Kotlin. When we continue, I will stop to describe another specific code on Kotlin.

2. Build Variants



In development, you often have different environments. The most standard is the test and production environment. These environments may differ in server URLs, icon, name, destination API, etc. On fleka in each project you have:

  • finalProduction , which is shipped to the Google Play Store.
  • demoProduction , that is, the version with the URL of the production server with new features that are still not in the Google Play Store. Our customers can install this version next to Google Play so they can test it and give us feedback.
  • demoTesting is the same as demoProduction with a test server URL.
  • mock , useful to me as a developer and designer. Sometimes we have a ready-made design, and our API is not ready yet. Waiting for an API to be started development is not a solution. This build option comes with fake data, so the design team can check it out and give us feedback. It is very useful not to put it off. When the API is ready, we move our development into a demoTesting environment .


In this application we will use all of them. They will have different applicationId and names. Gradle 3.0.0 has a new flavorDimension API that allows you to mix product varieties , so for example, you can mix demo and minApi23 varieties . In our application, we will use only the “default” flavorDimension. Go to build.gradle for the application and paste this code inside android {}

flavorDimensions "default"
productFlavors {
    finalProduction {
        dimension "default"
        applicationId "me.fleka.modernandroidapp"
        resValue "string", "app_name", "Modern App"
    }
    demoProduction {
        dimension "default"
        applicationId "me.fleka.modernandroidapp.demoproduction"
        resValue "string", "app_name", "Modern App Demo P"
    }
    demoTesting {
        dimension "default"
        applicationId "me.fleka.modernandroidapp.demotesting"
        resValue "string", "app_name", "Modern App Demo T"
    }
    mock {
        dimension "default"
        applicationId "me.fleka.modernandroidapp.mock"
        resValue "string", "app_name", "Modern App Mock"
    }
}

Go to strings.xml and delete the app_name line so that we don't have conflicts. Then click Sync Now . If you go to Build Variants , located to the left of the screen, you will see 4 build options, each of which has two build types: Debug and Release . Go to the demoProduction build option and run it. Then switch to another and run it. You should see two applications with different names.



3. ConstraintLayout


If you open activity_main.xml , you will see that this layout is ConstrainLayout . If you've ever written an app for iOS, you know about AutoLayout . ConstraintLayout really looks like him. They even use the same Cassowary algorithm.


Constraint helps us describe the relationships between View . For each View , you must have 4 Constraint , one for each side. In this case, our View is limited by the parent on each side.

If you move the TextView “Hello World” a little up in the Design tab , a new line will appear in the Text tab :

app:layout_constraintVertical_bias="0.28"



The Design and Text tabs are synchronized. Our changes in the Design tab affect the xml in the Text tab and vice versa. Vertical_bias describes the vertical view trend of his Constraint . If you want to center vertically, use:

app:layout_constraintVertical_bias="0.28"

Let's make our Activity show only one repository. It will have the name of the repository, the number of stars, the owner, and it will show whether the repository has issues or not.



To get such a layout, xml should look like this:


Let tools: text not bother you. It just helps us see a good preview of the layout.

You may notice that our layout is flat, even. There are no nested layouts. You should use nested layouts as little as possible, as this can affect performance. You can find more information about this here . In addition, ConstraintLayout works great with different screen sizes:



and it seems to me that I can achieve the desired result very quickly.
This was a small introduction to ConstraintLayout . You can find the Google code lab here , and the ConstraintLayout documentation on GitHub.

4. Data Binding Library


When I heard about the data binding library, the first question I asked myself was: “ ButterKnife works very well for me. In addition, I use a plugin that helps me get View from xml. Why should I change this?”. As soon as I learned more about data binding, I had the same feeling that I had when I first used ButterKnife .

How does ButterKnife help us?


ButterKnife helps us get rid of the boring findViewById . So, if you have 5 View, without Butterknife you have 5 + 5 lines to bind your View. With ButterKnife you have 5 lines. That's all.

What is bad at ButterKnife?


ButterKnife still does not solve the problem of code support. When I used ButterKnife, I often got a runtime exception because I deleted View in xml and did not delete the binding code in the Activity / Fragment class. Also, if you want to add View to xml, you need to bind again. This is very boring. You are wasting time keeping in touch.

What about a data binding library?


There are many benefits! With the data binding library, you can bind your View with just one line of code! Let me show you how it works. Let's add the Data Binding library to our project:

// at the top of file 
apply plugin: 'kotlin-kapt'
android {
    //other things that we already used
    dataBinding.enabled = true
}
dependencies {
    //other dependencies that we used
    kapt "com.android.databinding:compiler:3.0.0-beta1"
}

Note that the version of the Data Binding compiler must match the version of gradle in the build.gradle file of the project:

classpath 'com.android.tools.build:gradle:3.0.0-beta1'

Click Sync Now . Go to activity_main.xml and wrap the ConstraintLayout with the layout tag :


Note that you need to move all xmlns to the layout tag . Then click the Build icon or use the keyboard shortcut Ctrl + F9 ( Cmd + F9 on Mac). We need to build a project so that the Data Binding library can generate the ActivityMainBinding class , which we will use in our MainActivity class .



If you do not build the project, you will not see the ActivityMainBinding class , because it is generated at compile time. We still haven't finished binding, we just said that we have a non-zero variable of type ActivityMainBinding. In addition, as you can see, I did not indicate ? at the end of type ActivityMainBinding , and I did not initialize it. How is this possible? The lateinit modifier allows us to have non-zero variables awaiting initialization. Like ButterKnife , binding initialization should be done in the onCreate method when your Activity is ready. In addition, you should not declare a binding in the onCreate method , because you are probably using it outside the scope of the onCreate method . Our binding should not be null, so we use lateinit . Using the lateinit modifier, we don’t need to check the variable binding every time we access it.

Let's initialize our binding variable . You must replace:

setContentView(R.layout.activity_main)

on the:

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

That's all! You have successfully linked your View. Now you can access them and apply the changes. For example, let's change the repository name to “Modern Android Habrahabr Article”:

binding.repositoryName.text = "Modern Android Habrahabr Article"

As you can see, we can access all View (which have id , of course) from activity_main.xml via the binding variable . This is why Data Binding is better than ButterKnife .

Getters and Setters in Kotlin


You may have already noticed that we do not have a .setText () method , as in Java. I would like to stop here to explain how getters and setters work in Kotlin compared to Java.

First, you should know why we use setters and getters. We use them to hide class variables and allow access only using methods, so that we can hide class members from class clients and prevent the same clients from directly changing our class. Suppose we have a Square class in Java:

public class Square {
  private int a;
  Square(){
    a = 1;
  }
  public void setA(int a){
    this.a = Math.abs(a);
  }
  public int getA(){
    return this.a;
  }
}

Using the setA () method , we forbid clients of the class to set a negative value to the side of the square, it should not be negative. Using this approach, we must make a private, so it cannot be installed directly. It also means that the client of our class cannot get a directly, so we must provide a getter. This getter returns a . If you have 10 variables with similar requirements, you need to provide 10 getters. Writing such lines is a boring thing in which we usually do not use our minds.

Kotlin makes our developer’s life easier. If you call

var side: Int = square.a

this does not mean that you are accessing a directly. This is the same as

int side = square.getA();

in java. The reason is that Kotlin automatically generates default getters and setters. In Kotlin, you should specify a special setter or getter only if you have one. Otherwise, Kotlin will auto-generate it for you:

var a = 1
   set(value) { field = Math.abs(value) }

field ? What is it? To be clear, let's look at this code:

var a = 1
   set(value) { a = Math.abs(value) }

This means that you are calling the set method inside the set method , because there is no direct access to the property in the Kotlin world. This will create infinite recursion. When you call a = something , it automatically calls the set method.
I hope it’s now clear why you should use the field keyword and how setters and getters work.

Let's get back to our code. I would like to show you another great feature of the Kotlin language, apply :

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.apply {
            repositoryName.text = "Medium Android Repository Article"
            repositoryOwner.text = "Fleka"
            numberOfStarts.text = "1000 stars"
        }
    }
}

apply allows you to call multiple methods on a single instance.

We still have not finished data binding, there is still a lot to do. Let's create a user interface model class for the repository (this user interface model class for the GitHub repository stores the data that should be displayed, do not confuse it with the Repository pattern ). To make a Kotlin class, you must go to New -> Kotlin File / Class :

class Repository(var repositoryName: String?,var repositoryOwner: String?,var numberOfStars: Int? ,var hasIssues: Boolean = false)


In Kotlin, the primary constructor is part of the class header. If you do not want to provide a second constructor, that’s it! Your work on creating the class is completed here. There are no constructor parameters for field assignments, no getters and setters. A whole class in one line!

Return to the MainActivity.kt class and instantiate the Repository class :

var repository = Repository("Habrahabr Android Repository Article",
        "Fleka", 1000, true)

As you can see, you do not need the new keyword to build an object .

Now let's move on to activity_main.xml and add the data tag :


We can access the repository variable , which is a Repository type in our layout. For example, we can do the following in a TextView with the identifier repository_name :

android:text="@{repository.repositoryName}"

In the TextView repository_name will display the text from the properties repositoryName variable repository . It remains only to bind the repository variable from xml to repository from MainActivity.kt .
Click Build to generate the data binding library to create the necessary classes, return to MainActivity and add two lines:

binding.repository = repository
binding.executePendingBindings()

If you run the application, you will see the “Habrahabr Android Repository Article” appear in the TextView . Good feature, huh? :)

But what happens if we do the following:

Handler().postDelayed({repository.repositoryName="New Name"}, 2000)

Will the new text be displayed after 2 seconds? No, it will not be displayed. You must reset the repository value . Something like this will work:

Handler().postDelayed({repository.repositoryName="New Name"
    binding.repository = repository
    binding.executePendingBindings()}, 2000)

But it is boring if you need to do this every time we change some property. There is a better solution called Property Observer .
Let's first describe what the Observer pattern is , we will need this in the rxJava section : You

may have already heard about androidweekly.net . This is a weekly Android development newsletter. If you want to receive it, you need to subscribe to it by entering your email address. Later, if you want, you can stop unsubscribing from your site.

This is one example of an Observer / Observable pattern . In this case, Android Weekly is observable (Observable ), he issues newsletters every week. Readers are Observers , they subscribe to it, wait for new releases, and as soon as they receive it, they read it, and if some of them decide that they do not like it, he / she can stop watching.

The Property Observer , in our case, is an XML layout that will listen for changes in the Repository instance . Therefore, the repository is observable . For example, as soon as the name property of the Repository class changes in the class instance, xml should be updated without calling:

binding.repository = repository
binding.executePendingBindings()


How to do this using a data binding library? The data binding library provides us with the BaseObservable class , which must be implemented in the Repository class :

class Repository(repositoryName : String, var repositoryOwner: String?, var numberOfStars: Int?
                 , var hasIssues: Boolean = false) : BaseObservable(){
    @get:Bindable
    var repositoryName : String = ""
    set(value) {
        field = value
        notifyPropertyChanged(BR.repositoryName)
    }
}

BR is a class that is automatically generated once when the Bindable annotation is used . As you can see, as soon as a new value is set, we learn about it. Now you can start the application, and you will see that the repository name will be changed after 2 seconds without calling the executePendingBindings () function again .

That's all for this part. In the next part, I will write about the MVVM pattern, the Repository pattern, and the Android Wrapper Managers . You can find all the code here . This article covers code prior to this commit.

Only registered users can participate in the survey. Please come in.

Have you used Kotlin in Android development?

  • 58.5% No, while only reading 144
  • 2.8% I tried it for the first time in this project and have not yet decided whether to use it 7
  • 4% Yes, but decided to stay in Java 10
  • 14.2% Yes, I use with Java 35
  • 20.3% Yes, now I write only on it 50

Also popular now: