The author | Mladen Rakoajc translator thin west edit | | still fantle still fant thin west

Original address: proandroiddev.com/modern-andr…

preface

I often see some high quality technical posts on medium.com, but due to the Internet environment in China or some students are reluctant to read English, I miss many good articles. Therefore, Seeger decided to set up a “quality translation column” and spend some time translating some quality technical articles for everyone. This article is part of a four-part series on developing modern Android apps with Kotlin. The following is the text.

Now, it’s really hard to find a project that covers all the new Android technologies, so I decided to write one myself. In this article, we’ll use the following techniques:

  • 0 、Android Studio
  • 1. Kotlin
  • 2. Build variations
  • 3, ConstraintLayout
  • 4, DataBinding library
  • MVVM+ Repository +Android Manager
  • 6. RxJava2 and its help for architecture
  • Dagger 2.11, what is dependency injection? Why use it?
  • Retrofit + RxJava2 implements network requests
  • 9, RooM + RxJava2 implementation storage
What will our APP end up looking like?

Our APP is a very simple application that covers all of the technologies mentioned above. There is only one simple function: get all the repositories under the Googlesamples user from Github, save the data to a local database, and then display it in the interface.

I’ll try to explain more code, and you can also check out your code submission on Github.

Github:github.com/mladenrakon…

Let’s get started.

0, Android Studio

Android Studio 3 Beta 1 Kotlin is now supported by Android Studio 4.0. Go to the Create Android Project screen and you will see the new content here: Check box include Kotlin support with labels. By default, it is selected. Press Next twice, then select EmptyActivity, and you’re done. A: congratulations! You developed your first Android app with Kotlin.)

1, the Kotlin

In the newly created project, you can see a mainactivity.kt:

package me.mladenrakonjac.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)
    }
}
Copy the code

The.kt suffix indicates that this is a Kotlin file

MainActivity: AppCompatActivity() indicates that our MainActivity inherits from AppCompatActivity.

In Addition, all methods must have a fun keyword. In Kotlin, you cannot use the @override annotation. If you want to indicate that a method is a copy of a parent class or interface, use the Override keyword directly.

And then savedInstanceState: Bundle? In the? What does it stand for? It represents savedInstanceState which can be Bundle or NULL. Kotlin is a null security language, if you write it like this:

var a : String
Copy the code

You will get a compilation error. Since the a variable must be initialized and cannot be null, you would write it like this:

var a : String = "Init value"
Copy the code

Also, a compilation error will be reported if you do the following:

a = null
Copy the code

To make the a variable null, you must write:

var a : String?
Copy the code

Why is this an important feature of the Kotlin language? Androd developers are tired of NPE because it helps us avoid it, and even Null’s inventor, Mr. Tony Hoare, apologized for inventing it. Let’s say we have a nameTextView that can be null. If null, the following code will generate an NPE:

nameTextView.setEnabled(true)
Copy the code

But actually, Kotlin does so well that it doesn’t even allow us to do such things. It will force us to use it, right? Or!!!!! Operators. What if we use? Operator:

nameTextView? .setEnabled(true)
Copy the code

This line of code continues only if nameTextView is not null. Another case if we use!! Operator:

nameTextView!! .setEnabled(true)
Copy the code

If nameTextView is null, it will provide us with the NPE. It’s only for adventurous guys)

Here’s a little introduction to Kotlin. As we move on, I’ll stop describing other Kotlin-specific code.

2. Build variations

In general, in development, if you have two environments, the most common is a test environment and a production environment. These environments may differ in server urls, ICONS, names, target apis, and so on. I usually start each project with the following:

  • finalProduction: Upload to Google Play
  • demoProduction: This version uses production server urls and has new features that are not available on GP. Users can install it next to Google Play and then test new features and provide feedback.
  • demoTesting: The same as demoProduction, except it uses the test address
  • mock: Useful to me as a developer and designer. Sometimes we have designs ready and our apis are not ready. Waiting for the API to be ready before starting development is not a good solution. This build variant provides mock data so the design team can test it and provide feedback. It really helped to keep the project on track, and once the API was ready, we moved development to The demoTesting environment.

In this application, we will have all of these variations. Their applicationId and name are different. Gradle 3.0.0 flavourDimension has a new API that allows you to mix different product flavors, so you can mix Demo and minApi23 flavors. In our application, we will only use the “default” flavorDimension. In build.gradle of your app, insert this code into Android {} :

flavorDimensions "default"
    
productFlavors {

    finalProduction {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp"
        resValue "string"."app_name"."Modern App"
    }

    demoProduction {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp.demoproduction"
        resValue "string"."app_name"."Modern App Demo P"
    }

    demoTesting {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp.demotesting"
        resValue "string"."app_name"."Modern App Demo T"
    }


    mock {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp.mock"
        resValue "string"."app_name"."Modern App Mock"}}Copy the code

Open the string.xml file, delete the app_nameString resource, so we don’t have resource conflicts, and then click Sync Now. If you go to “Build variants” on the left side of the screen, you’ll see four different build variants, each of which has two build types: “Debug” and “Release”, switch to demoProduction to build the variant and run it. Then switch to another one and run it. You can see two applications with different names.

3, ConstraintLayout

If you open activity_main.xml, you can see that the layout is ConstraintLayout, and if you’ve developed iOS applications, you probably know AutoLayout, ConstraintLayout is very similar to this, They even used 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.mladenrakonjac.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" />
Copy the code

Constraints helps us describe the relationship between views. For each View, there should be four constraints, one on each side, in which case our View is constrained on each side of the superview.

In the Design Tab, if you move the Hello World text up slightly, the following line will be added to the TextTab:

app:layout_constraintVertical_bias="0.28"
Copy the code

The Design TAB and Text TAB are synchronized, and when we move the view in Design, we affect the XML in Text, and vice versa. Vertical bias describes the vertical trend of a view against its constraints. If you want to center the view vertically, use:

app:layout_constraintVertical_bias="0.28"
Copy the code

We have the Activity show only one repository, with the name of the repository, number of stars, author, and whether there is an issue

To get the above layout design, the code looks 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.mladenrakonjac.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>

Copy the code

Don’t be fooled by Tools: Text, which simply allows us to preview our layout.

We can note that our layout is flat without any nesting, and you should use layout nesting as little as possible because it affects our performance. ConstraintLayout also works well with different screen sizes.

I have a feeling we’ll get the layout we want soon.

Are but a few introduce something about ConstraintLayout above, you can see about ConstraintLayout use Google code lab: codelabs.developers.google.com/codelabs/co…

4. Data binding library

When I heard about the Data Binding library, my first thought was: Butterknife is as good as it gets, plus I’m using a plug-in to get views from XML, so why would I change to use a Data Binding? But as I learned more about Data Binding, I felt the same way I did when I first saw Butterknife.

What does Butterknife do for us?

ButterKnife helps us get rid of the boring findViewById. So, if you have five views and no Butterknife, you have 5 + 5 lines of code to bind your views. With ButterKnife, you only have our code. That’s it.

What are the downsides of Butterknife?

Butterknife still doesn’t solve the code maintainability problem, and I often find myself running into runtime exceptions with Butterknife because I remove views in the XML instead of the binding code in the Activity/Fragment class. Also, if you want to add views to the XML, you have to bind again. It’s really hard to maintain. You’ll waste a lot of time maintaining the View binding.

How does a Data Binding compare?

With Data Binding, you can bind a View in one line of code. Let’s see how it works. First, add a Data Binding to your 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: the compiler: 3.0.0 - beta1"
}
Copy the code

Note that the version of the data binding compiler is the same as the gradle version in the project build.gradle file:

classpath 'com. Android. Tools. Build: gradle: 3.0.0 - walk'
Copy the code

Then, click Sync Now, open activity_main.xml, and wrap the Constraint Layout 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.mladenrakonjac.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>

Copy the code

Note that you need to move all the XML under the Layout TAB and then click on the Build icon or use Cmd + F9. We need to Build the project to make the Data Binding library generate the ActivityMainBinding class for us. You’ll use it later in MainActivity.

If you don’t recompile the project, you won’t see the ActivityMainBinding because it is generated at compile time.

We haven’t finished binding yet, we just defined a non-empty variable of type ActivityMainBinding. Would you notice that I didn’t? Placed after the ActivityMainBinding and not initialized. How is that possible? The lateInit keyword allows us to use a variable that is not empty and delayed to be initialized. Similar to ButterKnife, after our layout is ready, the binding needs to be initialized in the onCreate method. In addition, you should not declare the binding in the onCreate method, because you are likely to use it outside of the onCreate method. Our binding cannot be empty, so that’s why we use LateInit. With the LateInit modifier, we don’t need to check whether the binding variable is empty every time we access it.

We initialize the binding variable and you need to replace it:

setContentView(R.layout.activity_main)
Copy the code

To:

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

That’s it, you successfully bound all the Views, now you can access it and make some changes, for example, we changed the repository name to Modern Android Medium Article:

binding.repositoryName.text = "Modern Android Medium Article"
Copy the code

As you can see, we can now access all views of main_activity. XML using the bingding variable (provided they have ids), which is why the Data Binding is better than ButterKnife.

Kotlin’s Getters and Setters

As you’ve probably noticed, we don’t use.settext () as Java does, and I want to pause here to illustrate how the getters and setters in Kotlin work compared to Java.

First of all, you need to know why we use getters and setters. We use getters to hide variables in our classes, allowing only methods to access them, so that we can hide details in our classes from the user and forbid the user to modify our classes directly. Suppose we write 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(a){
    return this.a; }}Copy the code

Using the setA() method, we disallow the user to setA negative number to the a variable of the Square class, because Square sides must be positive. To use this method, we must make it private, so we cannot set it directly. This also means that we can’t get a directly, we need to give it a get method to return A, if there are 10 variables, then we have to define 10 similar GET methods, and writing boilerplate code like this is usually bad for our mood.

Kotling made it easier for our developers. If you call the following code:

var side: Int = square.a
Copy the code

This does not mean that you are directly accessing the a variable; it is the same as calling getA() in Java

int side = square.getA();
Copy the code

Because Kotlin automatically generates default getters and setters. In Kotlin, you should only specify a special setter or getter if you have one. Otherwise, Kotlin will automatically generate for you:

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

field ? What is this thing again? For clarity, take a look at the following code:

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

This means that you are calling set(value){} in the set method, because in Kotlin’s world, there is no direct access to the property, and this causes infinite recursion. When you call a = something, the set method is automatically called. Using filed eliminates infinite recursion, and I hope that gives you an idea of why the field keyword is used and how getters and setters work.

Going back to the code, I’ll introduce you to another important feature of the Kotlin language: the apply function:

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 = "Mladen Rakonjac"
            numberOfStarts.text = "1000 stars"}}}Copy the code

Apply allows you to call multiple methods on an instance. We’re still not done with data binding, but even better, let’s define a UI model for the Repository (this is the Github Repository’s data model, which holds the data to be displayed, To create a Kotlin class, go to New -> Kotlin File/ class:

class Repository(varrepositoryName: String? .varrepositoryOwner: String? .var numberOfStars: Int? .var hasIssues: Boolean = false)
Copy the code

In Kotlin, the primary constructor is part of the class header, and if you don’t want to define a secondary constructor, that’s it, the data class is done here, the constructor has no arguments assigned to fields, no setters and getters, and the whole class is one line of code.

Go back to mainactivity. kt and create an instance of Repository.

var repository = Repository("Medium Android Repository Article"."Mladen Rakonjac".1000.true)
Copy the code

You’ll notice that creating class instances doesn’t use new

Now let’s add the data tag to activity_main.xml.

<data>
      <variable
        name="repository"
        type="me.mladenrakonjac.modernandroidapp.uimodels.Repository"
        />
</data>
Copy the code

We can access the repository variable stored in the layout. For example, we can use the TextView whose ID is Repository_name as follows:

android:text="@{repository.repositoryName}"
Copy the code

The Repository_NAME text view displays the text retrieved from the repository variable’s property repositoryName. The only thing left is to bind the repository variable from the XML to the repository in mainActivity.kt.

Click Build to make DataBinding generate the class for us, then add two lines of code to MainActivity:

binding.repository = repository
binding.executePendingBindings()
Copy the code

If you run the APP, you’ll see that TextView says: “Medium Android Repository Article”. Pretty cool, right?

But what if we change it like this?

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

Will the new text be displayed after 2000ms? No, you’ll have to reset the repository again, like this:

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

However, if we had to write this every time we changed a Property, which would be very painful, there is a better scheme called the Property Observer.

Let’s first explain what the Observer pattern is, because we’ll need it in the rxJava section as well:

You may have heard of http://androidweekly.net/, this is a weekly magazine about Android development. If you want to receive it, you must subscribe to it and provide your email address. After a while, if you don’t want to watch it anymore, you can go to the website and unsubscribe.

This is an observer/observed pattern, in this case Android Weekly is the observed, which publishes weekly newsletters. Readers are observers because they subscribe to it, receive data once they subscribe, and can stop subscribing if they don’t want to read it anymore.

The Property Observer, in this case an XML Layout, listens for changes to the Repository instance. Thus, Repository is observed. For example, once the Repository nane property is changed in an instance of the Repository class, the XML is updated without calling the following code:

binding.repository = repository
binding.executePendingBindings()
Copy the code

How do I get it to use the Data Binding library? The Data Binding library provides a BaseObservable class that our Repostory class must inherit from.

class Repository(repositoryName : String, varrepositoryOwner: String? .var numberOfStars: Int? .var hasIssues: Boolean = false) : BaseObservable(){

    @get:Bindable
    var repositoryName : String = ""
    set(value) {
        field = value
        notifyPropertyChanged(BR.repositoryName)
    }

}
Copy the code

When we use the Bindable annotation, the BR class is automatically generated. You will see that once the new value is set, it will be notified to update. Run the app now and you’ll see the name of the repository change 2 seconds later without having to call executePendingBindings() again.

That’s all for this section, and the next section will cover the use of the MVVM+Repository schema. Stay tuned! Thanks for reading.

This series has been updated:

Write a modern Android project -Part1 – from scratch using Kotlin

Write a modern Android project -Part2 – from scratch using Kotlin

Write a modern Android project -Part3 – from scratch using Kotlin

Write a modern Android project -Part4 – from scratch using Kotlin

The article was first published on the public account: “Technology TOP”, there are dry articles updated every day, you can search wechat “technology TOP” first time to read, reply [mind map] [interview] [resume] yes, I prepare some Android advanced route, interview guidance and resume template for you