The articles

Kotlin Jetpack: The Beginning

00. The Kotlin Pit Guide for Java Developers

01. Start with a Demo of worshiping a God

02. What is the Experience of Writing Gradle scripts with Kotlin?

Introduction to the

This article assumes that you already have the foundation of Kotlin. If you are not familiar with Kotlin, you can read my previous article.

This article will walk you through the step-by-step reconstruction of our Demo project with Kotlin and take a look at the triple realm of Kotlin programming.

Note: This series of articles deals only with Kotlin JVMS, not Kotlin JS/Native.

The main content

preparation

The first stage: Write Kotlin from a Java perspective

The second realm: Write Kotlin from Kotlin’s perspective

Third level: Write Kotlin from the Bytecode perspective

At the end

preparation

  • Update the Android Studio version to the latest
  • Clone our Demo project locally and open it with Android Studio:

Github.com/chaxiu/Kotl…

  • Switch to branch:chapter_03_kotlin_refactor_training
  • I strongly suggest that you follow this article to practice, the actual combat is the essence of this article

Add Kotlin support to the project

We changed Groovy to a Kotlin DSL in the previous chapter, but the project itself doesn’t support writing Android apps in Kotlin. So we also need to do some configuration:

Libs.kt adds the following dependency constants:

const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlinVersion}"
const val ktxCore = "androidx.core:core-ktx:${Versions.ktxCore}"
Copy the code

Build.gradle. kt added in the root directory:

dependencies {
    ...
    classpath(kotlin("gradle-plugin", version = Versions.kotlinVersion))
}
Copy the code

App/build. Gradle. Kt feature:

plugins {
    ...
    kotlin("android")
    kotlin("android.extensions")
}

dependencies {
    ...
    implementation(Libs.kotlinStdLib)
    implementation(Libs.ktxCore)
}
Copy the code

Note: The above configuration is sufficient for pure Kotlin development, but if you have Java hybrid development, it is best to add the following compiler parameters to prevent compatibility problems: app/build.gradle.kt

android {
    ...
    // Configure Java compiler compatible with Java 1.8
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // Configure Kotlin compiler target Java 1.8 when compile Kotlin to bytecode
    kotlinOptions {
        this as KotlinJvmOptions
        jvmTarget = "1.8"}}Copy the code

The functions of the above configurations are as follows:

  • Configure the Java compiler to be compatible with Java 1.8
  • Configure the Kotlin compiler to generate bytecode with the Java 1.8 specification

See my GitHub Commit for details on the above changes.

Let’s get down to business, refactoring Java code with Kotlin.

The body of the

I’ve always thought of Kotlin as an easy language to learn but hard to master: easy to get started, hard to master. If there are three realms for Kotlin programmers, I think there are three realms.

1. The first stage: Write Kotlin from a Java perspective

This is something that almost every Kotlin programmer experiences (including me). I thought I could write Kotlin code by learning Kotlin syntax, but I just translated the Java/C code in my head into Kotlin syntax.

I’m going to rebuild our Demo project using the “skills” of the first realm. Just watch the fun, don’t take it into your head. [head]

I’m pretending I’m new to Kotlin grammar. Is the so-called, persimmon to pick soft pinch, let’s rebuild the code from the simplest of course. So I found the Demo project user.java, a bite of teeth, you:

public class User {
    // The project is so simple that there is no database, so the API requests are cached here
    public static final String CACHE_RESPONSE = "{"login":"JakeWharton","id": 66577,"node_id":"MDQ6VXNlcjY2NTc3","avatar_url":"https://avatars0.githubusercontent.com/u/66577?v=4","gravatar_id":"","url":"https://api.github.com/users/JakeWharton","html_ur L ":" https://github.com/JakeWharton ", "followers_url" : "https://api.github.com/users/JakeWharton/followers," friend "following_u rl":"https://api.github.com/users/JakeWharton/following{/other_user}","gists_url":"https://api.github.com/users/JakeWhar ton/gists{/gist_id}","starred_url":"https://api.github.com/users/JakeWharton/starred{/owner}{/repo}","subscriptions_url" :"https://api.github.com/users/JakeWharton/subscriptions","organizations_url":"https://api.github.com/users/JakeWharton/ orgs","repos_url":"https://api.github.com/users/JakeWharton/repos","events_url":"https://api.github.com/users/JakeWharto n/events{/privacy}","received_events_url":"https://api.github.com/users/JakeWharton/received_events","type":"User","site _admin":false,"name":"Jake Wharton","company":"Square","blog":"https://jakewharton.com","location":"Pittsburgh, PA, USA","email":null,"hireable":null,"bio":null,"twitter_username":null,"public_repos":104,"public_gists":54,"followers":57 849,"following":12,"created_at":"2009-03-24T16:09:53Z","updated_at":"2020-05-28T00:07:20Z"}";

    private String id;
    private String login;
    private String avatar_url;
    private String name;
    private String company;
    private String blog;
    private Date lastRefresh;

    public User(a) {}public User(@NonNull String id, String login, String avatar_url, String name, String company, String blog, Date lastRefresh) {
        this.id = id;
        this.login = login;
        this.avatar_url = avatar_url;
        this.name = name;
        this.company = company;
        this.blog = blog;
        this.lastRefresh = lastRefresh;
    }

    public String getId(a) { return id; }
    public String getAvatar_url(a) { return avatar_url; }
    public Date getLastRefresh(a) { return lastRefresh; }
    public String getLogin(a) { return login; }
    public String getName(a) { return name; }
    public String getCompany(a) { return company; }
    public String getBlog(a) { return blog; }

    public void setId(String id) { this.id = id; }
    public void setAvatar_url(String avatar_url) { this.avatar_url = avatar_url; }
    public void setLastRefresh(Date lastRefresh) { this.lastRefresh = lastRefresh; }
    public void setLogin(String login) { this.login = login; }
    public void setName(String name) { this.name = name; }
    public void setCompany(String company) { this.company = company; }
    public void setBlog(String blog) { this.blog = blog; }
Copy the code

In a quick operation, I translated the Java Bean using Kotlin syntax like this:

class User {

    companion object {
        val CACHE_RESPONSE = "..."
    }

    private var id: String? = null
    private var login: String? = null
    private var avatar_url: String? = null
    private var name: String? = null
    private var company: String? = null
    private var blog: String? = null
    private var lastRefresh: Date? = null
    
    constructor() {}
    constructor(id: String, login: String? , avatar_url: String? , name: String? , company: String? , blog: String? , lastRefresh: Date?) {this.id = id
        this.login = login
        this.avatar_url = avatar_url
        this.name = name
        this.company = company
        this.blog = blog
        this.lastRefresh = lastRefresh
    }

    fun getId(a): String? { return id }
    fun getAvatar_url(a): String? { return avatar_url }
    fun getLastRefresh(a): Date? { return lastRefresh }
    fun getLogin(a): String? { return login }
    fun getName(a): String? { return name }
    fun getCompany(a): String? { return company }
    fun getBlog(a): String? { return blog }

    fun setId(id: String?). { this.id = id }
    fun setAvatar_url(avatar_url: String?). { this.avatar_url = avatar_url }
    fun setLastRefresh(lastRefresh: Date?). { this.lastRefresh = lastRefresh }
    fun setLogin(login: String?). { this.login = login }
    fun setName(name: String?). { this.name = name }
    fun setCompany(company: String?). { this.company = company }
    fun setBlog(blog: String?). { this.blog = blog }
}
Copy the code

I looked at my Kotlin code line by line and felt a sense of accomplishment. So easy! [head]

To enable the project to emulate the Kotlin/Java mashup, we left ImagePreviewActivity in the Java state, so the mainActivity.java refactoring was left. Let’s start with the Java code for MainActivity.

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "Main";
    public static final String EXTRA_PHOTO = "photo";

    StringRequest stringRequest;
    RequestQueue requestQueue;

    private ImageView image;
    private ImageView gif;
    private TextView username;
    private TextView company;
    private TextView website;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(a) {
        image = findViewById(R.id.image);
        gif = findViewById(R.id.gif);
        username = findViewById(R.id.username);
        company = findViewById(R.id.company);
        website = findViewById(R.id.website);

        display(User.CACHE_RESPONSE);
        requestOnlineInfo();
    }

    private void requestOnlineInfo(a) {
        requestQueue = Volley.newRequestQueue(this);
        String url ="https://api.github.com/users/JakeWharton";
        stringRequest = new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) { display(response); }},new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Toast.makeText(MainActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show(); }}); stringRequest.setTag(TAG); requestQueue.add(stringRequest); }private void display(@Nullable String response) {
        if (TextUtils.isEmpty(response)) { return; }

        Gson gson = new Gson();
        final User user = gson.fromJson(response, User.class);
        if(user ! =null){
            Glide.with(this).load("file:///android_asset/bless.gif").into(gif);
            Glide.with(this).load(user.getAvatar_url()).apply(RequestOptions.circleCropTransform()).into(image);
            this.username.setText(user.getName());
            this.company.setText(user.getCompany());
            this.website.setText(user.getBlog());

            image.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) { gotoImagePreviewActivity(user); }}); }}private void gotoImagePreviewActivity(User user) {
        Intent intent = new Intent(this, ImagePreviewActivity.class);
        intent.putExtra(EXTRA_PHOTO, user.getAvatar_url());
        startActivity(intent);
    }

    @Override
    protected void onStop (a) {
        super.onStop();
        if(requestQueue ! =null) { requestQueue.cancelAll(TAG); }}}Copy the code

With one operation, I refactor MainActivity to look like this:

class MainActivity : AppCompatActivity() {
    companion object {
        val TAG = "Main"
        val EXTRA_PHOTO = "photo"
    }

    var stringRequest: StringRequest? = null
    var requestQueue: RequestQueue? = null

    private var image: ImageView? = null
    private var gif: ImageView? = null
    private var username: TextView? = null
    private var company: TextView? = null
    private var website: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        init()}private fun init(a) {
        image = findViewById(R.id.image)
        gif = findViewById(R.id.gif)
        username = findViewById(R.id.username)
        company = findViewById(R.id.company)
        website = findViewById(R.id.website)
        display(User.CACHE_RESPONSE)
        requestOnlineInfo()
    }

    private fun requestOnlineInfo(a) {
        requestQueue = Volley.newRequestQueue(this)
        val url = "https://api.github.com/users/JakeWharton"
        stringRequest = StringRequest(Request.Method.GET, url,
                object: Response.Listener<String> {
                    override fun onResponse(response: String?). {
                        display(response)
                    }
                }, object: Response.ErrorListener {
            override fun onErrorResponse(error: VolleyError?). {
                Toast.makeText(this@MainActivity, error? .message, Toast.LENGTH_SHORT).show() } }) stringRequest!! .tag = TAG requestQueue!! .add(stringRequest) }private fun display(response: String?). {
        if (TextUtils.isEmpty(response)) {
            return
        }
        val gson = Gson()
        val user = gson.fromJson(response, User::class.java)
        if(user ! =null) {
            Glide.with(this).load("file:///android_asset/bless.gif").into(gif!!)
            Glide.with(this).load(user.getAvatar_url()).apply(RequestOptions.circleCropTransform()).into(image!!) username!! .text = user.getName() company!! .text = user.getCompany() website!! .text = user.getBlog() image!! .setOnClickListener(object: View.OnClickListener{
                override fun onClick(v: View?). {
                    gotoImagePreviewActivity(user)
                }
            })
        }
    }

    private fun gotoImagePreviewActivity(user: User) {
        val intent = Intent(this, ImagePreviewActivity::class.java)
        intent.putExtra(EXTRA_PHOTO, user.getAvatar_url())
        startActivity(intent)
    }

    override fun onStop(a) {
        super.onStop()
        if(requestQueue ! =null) { requestQueue!! .cancelAll(TAG) } } }Copy the code

Due to MainActivity reconstruction became Kotlin, ImagePreviewActivity. Java need corresponding to do some adjustments. The reason is that Java does not recognize associated objects very well.

Modify before:

public class ImagePreviewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); . String url = intent.getStringExtra(MainActivity.EXTRA_PHOTO); . }}Copy the code

Revised:

public class ImagePreviewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); . String url = intent.getStringExtra(MainActivity.Companion.getEXTRA_PHOTO()); . }}Copy the code

summary

This realm is characterized by a line of Kotlin corresponding to a line of Java, without using kotlin-specific features.

See my GitHub Commit for details on the above changes.

Dear friends do not see here to walk ah, look at my next realm is how to write (play).

2. The second realm: Write Kotlin from the perspective of Kotlin

At the second level, I’m a full-fledged Kotlin programmer. I’ll use some Kotlin specific features to improve the logic in Java code.

2-1 Data Class

Let’s start with the simplest User. Kt file. If you have read Kotlin’s Guide to Java Developers, you must know the Data Class.

data class User(
        var id: String? = null.var login: String? = null.var avatar_url: String? = null.var name: String? = null.var company: String? = null.var blog: String? = null.var lastRefresh: Date? = null
) {
    companion object {
        val CACHE_RESPONSE = "..."}}Copy the code
summary

The Data Class saves us time writing Java beans.

2-2 lateinit

Next, mainActivity.kt, we start with the top variable. All of the variables we defined were Nullable, resulting in Nullable or non-null assertions being used. . That’s not Kotlin. There are many ways to solve this problem, but here I’ll start with LateInit to solve two variables of the network request.

Modify before:

class MainActivity : AppCompatActivity() {...var stringRequest: StringRequest? = null
    var requestQueue: RequestQueue? = null

    private fun requestOnlineInfo(a){... stringRequest!! .tag = TAG requestQueue!! .add(stringRequest) } }Copy the code

Revised:

class MainActivity : AppCompatActivity() {...private lateinit var stringRequest: StringRequest
    private lateinit var requestQueue: RequestQueue

    private fun requestOnlineInfo(a){... stringRequest.tag = TAG requestQueue.add(stringRequest) } }Copy the code

summary

In general, variables that are not null need to be assigned in the constructor or init block so that the compiler doesn’t get an error. But a lot of times our variable assignments don’t work in these cases, like findViewById.

Lateinit is telling the compiler that this variable THAT I’ve defined that is not empty, I haven’t assigned it yet, but before I use it, I’m going to assign it, it’s definitely not empty, you don’t have to get an error.

2-3 Kotlin-Android-Extensions

KTX is a Gradle plugin for Android that provides convenience to developers. It is best known for its ability to dispense with findViewById. We have already added this plug-in in the project, so we can use it directly.

Delete the declaration and assignment of the control directly, and then press Option + return to select import from the call:

Modify before:

private var image: ImageView? = null
private var gif: ImageView? = null
private var username: TextView? = null
private var company: TextView? = null
private var website: TextView? = nullimage = findViewById(R.id.image) gif = findViewById(R.id.gif) username = findViewById(R.id.username) company = findViewById(R.id.company) website = findViewById(R.id.website) ... username!! .text = user.name company!! .text = user.company website!! .text = user.blogCopy the code

Revised:

// Notice here
import kotlinx.android.synthetic.main.activity_main.*

// private var image: ImageView? = null
// private var gif: ImageView? = null
// private var username: TextView? = null
// private var company: TextView? = null
// private var website: TextView? = null

// image = findViewById(R.id.image)
// gif = findViewById(R.id.gif)
// username = findViewById(R.id.username)
// company = findViewById(R.id.company)
// website = findViewById(R.id.website). username.text = user.name company.text = user.company website.text = user.blogCopy the code

summary

  • KTX certainly provides more convenience than just replacing findViewById, which we’ll talk about later
  • While KTX offers convenience, there are certain thingsHidden troubleWe’ll talk about that later

2-4 Lambda

The following code will prompt Android Studio to Convert to lambda. We just need to press Option + Return and Android Studio will help us refactor.

Modify before:

. stringRequest = StringRequest(Request.Method.GET, url,object : Response.Listener<String> {
            override fun onResponse(response: String?). {
                display(response)
            }
        }, object : Response.ErrorListener {
    override fun onErrorResponse(error: VolleyError?). {
        Toast.makeText(this@MainActivity, error? .message, Toast.LENGTH_SHORT).show() } }) ... image.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?). {
        gotoImagePreviewActivity(user)
    }
})
Copy the code

Revised:

. stringRequest = StringRequest(Request.Method.GET, url, Response.Listener { response -> display(response) }, Response.ErrorListener { error -> Toast.makeText(this@MainActivity, error? .message, Toast.LENGTH_SHORT).show() }) ... image.setOnClickListener { gotoImagePreviewActivity(user) } ...Copy the code

summary

  • How to use Kotlin Lambda for the moment
  • The biggest benefit of using Lambda as an interface implementation here is that it actually improves the codereadability

2-5 Extension functions

Use Kotlin’s extension functions to wipe out any xxutils.java. The Kotlin standard functions already provide extension functions to help eliminate TextUtils.

Modify before:

.if (TextUtils.isEmpty(response)) {
    return
}
Copy the code

Revised:

.if (response.isNullOrBlank()) {
    return
}
Copy the code

The modified code above looks like response has a member method: isNullOrBlank(), which has a number of benefits:

  • Writing code is smoother, and the IDE will automatically indicate which methods a class can call instead of looking for xxUtils
  • Better code readability

2-6 Standard functions apply

Kotlin provides a series of standard functions such as let, Also, with, and apply to help developers simplify their logic. Here we use apply, which is cumbersome to explain and makes the code clearer:

Modify before:

if(user ! =null) {... username.text = user.name website.text = user.blog image.setOnClickListener { gotoImagePreviewActivity(user) } }Copy the code

Revised:

user? .apply { ... username.text = name website.text = blog image.setOnClickListener { gotoImagePreviewActivity(this)}}Copy the code

summary

This realm is characterized by:

  • One line of Kotlin code can correspond to multiple lines of Java code
  • Code readability enhancement
  • Better code robustness

See the Github Commit for details.

Third level: Write Kotlin from the Bytecode perspective

Kotlin claims Java is 100% compatible because Kotlin will eventually be compiled into Bytecode. By looking at Kotlin’s compiled bytecodes, we can both understand how Kotlin works and explore some of Kotlin’s programming Tips.

Due to the limited space of this article, we won’t discuss how Kotlin is implemented or explore Kotlin programming Tips in detail. We continue to focus on the field. In the current project, we have tried to add some Kotlin features, and we will only study those Kotlin features that are used at this stage.

3-1 How do I view the bytecode corresponding to Kotlin?

Tools -> Kotlin -> Show Kotlin Bytecode -> Show Kotlin Bytecode -> Show Kotlin Bytecode -> Show Kotlin Bytecode So you can see Kotlin’s equivalent Java code.

3-2 Eliminate Mutability whenever possible

Variables modified by final in Java cannot be modified once assigned. This is also a good habit in Java and should be used in Kotlin. Kotlin doesn’t have a final, but it has a val.

Let’s start with User.kt.

data class User(
        var id: String? = null.var login: String? = null.var avatar_url: String? = null.var name: String? = null.var company: String? = null.var blog: String? = null.var lastRefresh: Date? = null
) {
    companion object {
        val CACHE_RESPONSE = "..."}}Copy the code

User.kt decompiled into Java:

.public final class User {
   @Nullable
   privateString id; .@NotNull
   private static final String CACHE_RESPONSE = "...";
   public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null);

   @Nullable
   public final String getId(a) {
      return this.id;
   }

   public final void setId(@Nullable String var1) {
      this.id = var1; }...public static final class Companion {
      @NotNull
      public final String getCACHE_RESPONSE(a) {
         return User.CACHE_RESPONSE;
      }

      private Companion(a) {}// $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this(a); }}}Copy the code

Let’s replace all var in user.kt with val:

data class User(
        val id: String? = null.val login: String? = null.val avatar_url: String? = null.val name: String? = null.val company: String? = null.val blog: String? = null.val lastRefresh: Date? = null
) {
    companion object {
        val CACHE_RESPONSE = "..."}}Copy the code

It decompiled into Java code to look like this:

public final class User {
   @Nullable
   private final String id; / / the final.@NotNull
   private static final String CACHE_RESPONSE = "...";
   public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null);

   @Nullable
   public final String getId(a) {
      return this.id;
   }
   // setId() is not available.public static final class Companion {
      @NotNull
      public final String getCACHE_RESPONSE(a) {
         return User.CACHE_RESPONSE;
      }

      private Companion(a) {}// $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this(a); }}}Copy the code

Summary:

  • Kotlin is based on the JVM, so previous Java programming experience is also useful
  • When we change the var of a Data Class to val, its member variables are final, and the set method is gone. Once a Data Class is instantiated, it cannot be modified
  • What if you want to modify a Data Class member variable? With the copy method

Minimize the Scope of variables

This is also useful in Java and Kotlin. There are two member variables in mainActivity. kt where stringRequest can actually be changed to a local variable.

Modify before:

class MainActivity : AppCompatActivity() {...private lateinit var stringRequest: StringRequest
    private lateinit var requestQueue: RequestQueue
}
Copy the code

Revised:

class MainActivity : AppCompatActivity() {...// private lateinit var stringRequest: StringRequest
    private lateinit var requestQueue: RequestQueue
    
    private fun requestOnlineInfo(a){...val stringRequest = StringRequest(Request.Method.GET,
            url,
            Response.Listener { response ->
                display(response)
            },
            Response.ErrorListener { error ->
                Toast.makeText(this@MainActivity, error?.message, Toast.LENGTH_SHORT).show()
            })
    ...
    }
}
Copy the code

3. By lazy

MainActivity only has one member variable left, requestQueue, which is decorated with var. Can we change it to val? Sure, but we need by lazy delegate.

Revised:

class MainActivity : AppCompatActivity() {...private val requestQueue: RequestQueue by lazy { 
        Volley.newRequestQueue(this)}}Copy the code

Let’s look at its equivalent Java code, which is initialized to lazykt.lazy:

private final Lazy requestQueue$delegate = LazyKt.lazy((Function0)(new Function0() {
  // $FF: synthetic method
  // $FF: bridge method
  public Object invoke(a) {
     return this.invoke();
  }

  public final RequestQueue invoke(a) {
     return Volley.newRequestQueue((Context)MainActivity.this); }}));Copy the code

Consider the implementation of lazykt. lazy, which is SynchronizedLazyImpl:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
Copy the code

Look again at SynchronizedLazyImpl:

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private vallock = lock ? :this

    override val value: T
        get() {
            val _v1 = _value
            if(_v1 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if(_v2 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    valtypedValue = initializer!! () _value = typedValue initializer =null
                    typedValue
                }
            }
        }

    override fun isInitialized(a): Boolean= _value ! == UNINITIALIZED_VALUEoverride fun toString(a): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(a): Any = InitializedLazyImpl(value)
}
Copy the code

Sure enough, as mentioned in our previous article, by Lazy is initialized synchronously by default. This is not necessary for our current project, as multithreaded synchronization is also expensive.

Revised:

private val requestQueue: RequestQueue by lazy(LazyThreadSafetyMode.NONE) {
    Volley.newRequestQueue(this)}Copy the code

3-5 Do not use the wrong companion

Since Java does not recognize the associated objects in Kotlin, it is awkward to access them in Java.

class MainActivity : AppCompatActivity() {
    companion object{...val EXTRA_PHOTO = "photo"}}Copy the code

Access in Java:

public class ImagePreviewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {... String url = intent.getStringExtra(MainActivity.Companion.getEXTRA_PHOTO()); . }}Copy the code

After decompiling:

.@NotNull
private static final String EXTRA_PHOTO = "photo";
public static final MainActivity.Companion Companion = new MainActivity.Companion((DefaultConstructorMarker)null); .public static final class Companion {
  @NotNull
  public final String getEXTRA_PHOTO(a) {
     return MainActivity.EXTRA_PHOTO;
  }

  private Companion(a) {}// $FF: synthetic method
  public Companion(DefaultConstructorMarker $constructor_marker) {
     this();
  }
}
Copy the code

We can see that, by default, the Kotlin generated the get method for associated variables in the object, the Java code to access the variable must be like this: MainActivity.Com panion. GetEXTRA_PHOTO (), it is not very friendly.

To enable Java to better recognize variables and methods in associated objects, we can do this:

Use the const:

class MainActivity : AppCompatActivity() {
    companion object{...const val EXTRA_PHOTO = "photo"}}Copy the code

Or use the @jVMField annotation:

class MainActivity : AppCompatActivity() {
    companion object{...@JvmField
        val EXTRA_PHOTO = "photo"}}Copy the code

Access in Java:

public class ImagePreviewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {... String url = intent.getStringExtra(MainActivity.EXTRA_PHOTO); . }}Copy the code

The decompiled Java code in both cases looks like this:

.@NotNull
public static final String EXTRA_PHOTO = "photo";
public static final MainActivity.Companion Companion = new MainActivity.Companion((DefaultConstructorMarker)null); .public static final class Companion {
  @NotNull
  public final String getTAG(a) {
     return MainActivity.TAG;
  }

  private Companion(a) {}// $FF: synthetic method
  public Companion(DefaultConstructorMarker $constructor_marker) {
     this();
  }
}
Copy the code

Many bloggers say that’s where the companion ends. @jVMField, const, @jVMStatic, those are really the things to be careful about when you’re using associated objects.

But isn’t our code perfect at this point? Don’t.

We can see that even if we add @jVMField or const, the Companion object still generates get methods for constants, and defines a Companion class, as well as an instance. However, our initial requirement was simply to define a public Static Final String constant.

The title of this little summary is Don’t use the wrong companion. What is its premise? The premise is: should you use it? I can’t help but ask: Is there really a need for a partner in this situation? The answer is: no.

The TAG in MainActivity does not need to be accessed outside of the class, so it can be defined directly as a member variable:

class MainActivity : AppCompatActivity() {
    private val TAG = "Main"
}
Copy the code

Now that we’re left with EXTRA_PHOTO, what should we do with it? In Java, we often define a class to store constants, and we can use Kotlin to do the same:

Let’s create a constant.kt:

// Notice here that it comes before the package
@file:JvmName("Constant")

package com.boycoder.kotlinjetpackinaction

const val EXTRA_PHOTO = "photo"

const val CACHE_RESPONSE = "..."
Copy the code

In Kotlin you can use it directly like this:

// Kotlin can even omit Constant because CACHE_RESPONSE is a top-level Constant.
display(CACHE_RESPONSE)
Copy the code

Use this in Java:

// Constant.EXTRA_PHOTO is also well accessible in Java thanks to @file:JvmName("Constant")
String url = intent.getStringExtra(Constant.EXTRA_PHOTO);
Copy the code

Constant.kt decompilated to Java looks like this:

public final class Constant {
  @NotNull
  public static final String EXTRA_PHOTO = "photo";
  @NotNull
  public static final String CACHE_RESPONSE = "...";
}
Copy the code

So, if you just need to define static constants, why bother with Kotlin’s companion objects?

See my Github Commit for details on the above changes.

Conclusion:

  • Java programming experience is also useful in Kotlin, but we can’t be trapped by Java programming experience
  • Kotlin introduces features and concepts that are not available in Java, and it is best to understand the underlying implementation before using it
  • I wrote a blog on the InternetBest practicesNot necessarily (including this article), think for yourself

4. At the end

This article only uses our Demo to glimpse the threefold realm of Kotlin programming, so that we have an understanding of Kotlin programming as a whole. I’ll probably write a feature article later on the Hitchhiker’s Guide to the Kotlin Compiler, The Kotlin Best Practices Point North, maybe.

The article says that this is nearing the end, so is our Demo project perfected to this extent? Of course not. But I don’t want to write, you are welcome to leave a comment and discuss what can be improved.

See you in the next article.

Kotlin Jetpack Combat