This article is a translation of the article from Mladen RakonjacIt is very difficult to find one project that would cover everything new in the development for Android in Android Studio 3.0, so I decided to write it. In this article we will sort the following:

')
- Android Studio 3
- Kotlin programming language
- Assembly options
- ConstraintLayout
- Data Binding Library
- MVVM architecture + repository pattern (with mappers) + Android Manager Wrappers
- RxJava2 and how it helps us in architecture
- Dagger 2.11, what is dependency injection, why should you use it.
- Retrofit (Rx Java2)
- Room (Rx Java2)
What will our application be?
Our application will be the simplest, which covers all of the above things: it will have only one function that extracts all the
googlesamples user
repositories from GitHub, stores this data in a local database and displays it to the user.
I will try to explain as many lines of code as possible. You can always see the code 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 checkbox labeled
Include Kotlin support . It is selected by default. Double-click the
Next button and select
Empty Activity , then click
Finish .
Congratulations! You made the first Android application 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 must use the
override keyword, not an annotation, as in Java.
So what does it mean
? in
savedInstanceState: Bundle? ? This means that the savedInstanceState can be of type
Bundle or of type
null .
Kotlin is a null 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 should 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 this important feature of Kotlin language? This helps us avoid
NPE . Android developers are already tired of NPE. Even the creator of the 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 an NPE:
nameTextView.setEnabled(true)
But Kotlin, in fact, is good, he will not allow us to do even that. Does he make us use the operator
? or operator
!! . If we use the operator
? :
nameTextView?.setEnabled(true)
The string will only be executed 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 :).
It was a small introduction to Kotlin. When we continue, I’ll 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, target API, etc. On
fleka in every project you have:
- finalProduction , which is sent 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 that they can test it and give us feedback.
- demoTesting , the same as demoProduction with the test URL of the server.
- mock , useful for me as a developer and designer. Sometimes we have a ready-made design, and our API is not yet ready. Waiting for the API to start development is not a solution. This version of the assembly is equipped with fake data, so the design team can check it out and give us feedback. It is very useful not to postpone it. When the API is ready, we move our development to the demoTesting environment.
In this application we will use all of them. They will have different applicationId and names. In gradle 3.0.0, there is a new
flavorDimension API that allows you to mix product
varieties , for example, you can mix
demo and
minApi23 . In our application, we will use only “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 string so that we do not have conflicts. Then click
Sync Now . If you go to
Build Variants to the left of the screen, you will see 4 build options, each of which has two types of build:
Debug and
Release . Go to the
demoProduction build
option and run it. Then switch to another one and launch 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 have ever written an iOS application, you know about
AutoLayout .
ConstraintLayout really looks like him. They even use the same Cassowary algorithm.
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="me.fleka.modernandroidapp.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
Constraint helps us describe the connections between the
View . For each
View you should have 4
Constraint , one for each side. In this case, our
View is limited to a parent on each side.
If you move
TextView “Hello World” up a little in the
Design tab, a new line will appear in the
Text tab:
app:layout_constraintVertical_bias="0.28"
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 trend of view of its
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 any issues or not.

To get this layout, the xml should look like this:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="me.fleka.modernandroidapp.MainActivity"> <TextView android:id="@+id/repository_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.083" tools:text="Modern Android app" /> <TextView android:id="@+id/repository_has_issues" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="@string/has_issues" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/repository_name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toEndOf="@+id/repository_name" app:layout_constraintTop_toTopOf="@+id/repository_name" app:layout_constraintVertical_bias="1.0" /> <TextView android:id="@+id/repository_owner" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_name" app:layout_constraintVertical_bias="0.0" tools:text="Mladen Rakonjac" /> <TextView android:id="@+id/number_of_starts" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_owner" app:layout_constraintVertical_bias="0.0" tools:text="0 stars" /> </android.support.constraint.ConstraintLayout>
Let
tools: text not confuse you. It simply helps us to see a good preview of the layout.
You may notice that our layout is flat, smooth. There are no nested layouts. You should use nested layouts as rarely as possible, as this may affect performance. More information about this can be found
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. Library Data Binding
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 a
View from xml. Why should I change it?". 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 link your View. With ButterKnife you have 5 lines. That's all.
What is bad in ButterKnife?
ButterKnife still does not solve the problem of code support. When I used ButterKnife, I often get an exception at runtime, because I deleted the View in xml, and did not delete the binding code in the Activity / Fragment class. In addition, if you want to add a View in xml, you need to bind again. This is very boring. You lose time keeping in touch.
What about the data binding library?
There are many benefits! With the data binding library you can link 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" }
Please note that the version of the
Data Binding compiler must match the version of gradle in the project's
build.gradle file:
classpath 'com.android.tools.build:gradle:3.0.0-beta1'
Click
Sync Now . Go to
activity_main.xml and wrap
ConstraintLayout with the
layout tag:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context="me.fleka.modernandroidapp.MainActivity"> <TextView android:id="@+id/repository_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.083" tools:text="Modern Android app" /> <TextView android:id="@+id/repository_has_issues" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="@string/has_issues" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/repository_name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toEndOf="@+id/repository_name" app:layout_constraintTop_toTopOf="@+id/repository_name" app:layout_constraintVertical_bias="1.0" /> <TextView android:id="@+id/repository_owner" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_name" app:layout_constraintVertical_bias="0.0" tools:text="Mladen Rakonjac" /> <TextView android:id="@+id/number_of_starts" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_owner" app:layout_constraintVertical_bias="0.0" tools:text="0 stars" /> </android.support.constraint.ConstraintLayout> </layout>
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 specify
? at the end of the
ActivityMainBinding type, and I haven't initialized it. How is this possible? The
lateinit modifier allows us to have nonzero 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 probably use it outside the scope of the
onCreate method. Our binding should not be null, so we use
lateinit . Using the
lateinit modifier, we do not need to check the binding of a variable every time we access it.
Let's initialize our
binding variable. You must replace:
setContentView(R.layout.activity_main)
on:
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
That's all! You have successfully linked your View. Now you can access 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 an
id , of course) from
activity_main.xml through the
binding variable. That 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 need to know why we use setters and getters. We use them to hide class variables and allow access only through 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 prohibit class clients from setting a negative value to the side of a square; it should not be negative. Using this approach, we need to make
a private, so it cannot be installed directly. It also means that the client of our class cannot get
a directly, so we have to provide 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 mind.
Kotlin makes life easier for our developer. If you call
var side: Int = square.a
this does not mean that you access
a directly. This is the same as
int side = square.getA();
in java. The reason is that Kotlin automatically generates getters and setters by default. In Kotlin, you must specify a special setter or getter, only if you have one. Otherwise, Kotlin autogenerates 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 call the
set method inside the
set method, because there is no direct access to a property in the Kotlin world. This will create endless recursion. When you call
a = something , it automatically calls the set method.
I hope it’s clear now why you should use the
field keyword and how setters and getters work.
Let's return 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 haven't 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 them 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 all! Your class creation work is completed here. There are no constructor parameters for field assignments, no getters and setters. A whole class on one line!
Return to the
MainActivity.kt class and create an instance of the
Repository class:
var repository = Repository("Habrahabr Android Repository Article", "Fleka", 1000, true)
As you can see, to build an object, you do not need the keyword
new .
Now let's move on to
activity_main.xml and add a
data tag:
<data> <variable name="repository" type="me.fleka.modernandroidapp.uimodels.Repository" /> </data>
We can access the
repository variable, which is the
Repository type in our layout. For example, we can do the following in a
TextView with the id
repository_name :
android:text="@{repository.repositoryName}"
The
TextView repository_name will display the text obtained from the
repositoryName property of the
repository variable. It remains only to associate the repository variable from xml to
repository from
MainActivity.kt .
Click
Build to generate the data binding library to create the required classes, go back to
MainActivity and add two lines:
binding.repository = repository binding.executePendingBindings()
If you run the app, you will see that the Habrahabr Android Repository Article appears in the
TextView . Good feature, yes? :)
But what happens if we do the following:
Handler().postDelayed({repository.repositoryName="New Name"}, 2000)
Will the new text appear after 2 seconds? No, it will not be displayed. You must reinstall the
repository value. Something like this will work:
Handler().postDelayed({repository.repositoryName="New Name" binding.repository = repository binding.executePendingBindings()}, 2000)
But it's boring if you need to do it every time we change a property. There is a better solution called
Property Observer .
Let's first describe what the
Observer pattern is, we'll need it in the
rxJava section:
You may have heard about
androidweekly.net . This is a weekly newsletter about Android development. 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 the
Observer /
Observable pattern. In this case, Android Weekly is
Observable , it releases newsletters every week. Readers are
Observers , they subscribe to it, wait for new issues, and as soon as they receive it, they read it, and if some of them decide that they don’t like it, he / she can stop following.
Property Observer , in our case, is an XML layout that will listen for changes in the
Repository instance. Thus, the
Repository is
observable . For example, as soon as the
name property of the
Repository class changes in an instance of the class, the xml should be updated without calling:
binding.repository = repository binding.executePendingBindings()
How to do this using the 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 the new value is set, we will know about it. Now you can run the application, and you will see that the repository name will be changed after 2 seconds without calling the function executePendingBindings () again .For this part, that's all. In the next part, I'll write about the MVVM pattern, the Repository pattern, and Android Wrapper Managers . You can find all the code here . This article covers the code to this commit.