preface

I’ve heard of Kotlin for a long time, and as an Android coder, I want to keep up with Google’s push for Kotlin. It happened to be a major overhaul of the company’s project, so we decided to replace Java with Kotlin and refactor it with MVP+Retrofit+RxJava framework, because the old code was too difficult to maintain. During the refactoring process, it was a bit frustrating to find a way to retrieve intent parameters. I wondered if there was a better way to do it.

Parameter acquisition method before reform and opening up

In Java, we get the passed argument like this:

Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String name = bundle.getString("name");
Copy the code

In Kotlin, we get passed parameters like this:

val bundle = intent.extras
val name = bundle.getString("name")
Copy the code

We find that getting passed parameters, whether in Java or Kotlin, requires fetching and assigning parameters in onCreate() or somewhere else. If we pass a lot of parameters, we write a lot of duplicate code. In Java, we can use annotations to assign values to parameters. Is there an annotation in Kotlin? There is no need for annotations. Kotlin supports extension functions and delegate properties, which can be used to bind parameters without assigning values to them in onCreate().

Entrusted property

Before we talk about how to use extension functions and delegate properties to implement parameter binding, we need to understand these two features first. Extension functions are relatively simple, so we will not talk about them here, but let’s talk about delegate properties.

class Example {
    var p: String by Delegate()
}
Copy the code

This is a simple Delegate property, in which the set and get methods of property P are delegated to a Delegate.

The syntax is: val/var < attribute name >: < type > by < expression >. The expression after BY is the delegate, because the property’s corresponding get() (and set()) are delegated to its getValue() and setValue() methods.

So let’s look at this Delegate.

class Delegate { operator fun getValue(thisRef: Any? , property: KProperty<*>): String {return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any? , property: KProperty<*>, value: String) { println("$value has been assigned to '${property.name}' in $thisRef.")}}Copy the code

If the type is val, a getValue method is provided, and if the type is var, a setValue method is provided. The first argument is an object of its own, and the second argument holds a description of the object. When we access the delegate property, the delegate’s getValue method is called. For a more detailed description of delegate properties, visit Delegate Properties.

Parameter binding is realized by extension function and delegate attribute

Extensions.kt

fun <U, T> Activity.bindExtra(key: String) = BindLoader<U, T>(key)

fun <U, T> Fragment.bindArgument(key: String) = BindLoader<U, T>(key)

fun <U, T> android.app.Fragment.bindArgument(key: String) = BindLoader<U, T>(key)

private class IntentDelegate<in U, out T>(private val key: String) : ReadOnlyProperty<U, T> {
    override fun getValue(thisRef: U, property: KProperty<*>): T {
        @Suppress("UNCHECKED_CAST")
        returnwhen (thisRef) { is Fragment -> thisRef.arguments? .get(key) as T is android.app.Fragment -> thisRef.arguments? .get(key) as Telse -> (thisRef as Activity).intent?.extras?.get(key) as T
        }
    }

}

class BindLoader<inU, out T>(private val key: String) { operator fun provideDelegate(thisRef: U, prop: KProperty<*>): ReadOnlyProperty<U, T> {// Create a delegatereturn IntentDelegate(key)
    }

}
Copy the code

Here for Activity, android. App. Fragments, android. Support. The v4. App. The fragments are expanding, the main implementation logic in IntentDelegate are, ThisRef determines the type and executes the appropriate method to get the parameters, and finally completes the assignment.

How to use

After we need to bind the parameters of the parameter binding can be done, very simple and convenient, simply refreshing do not do not. If it has a value, it will return the corresponding parameter value. If it has no value, it will return null.

To use in an Activity:

private val id: Int by bindExtra("id")
private val name: String by bindExtra("name")
Copy the code

Use in Fragment:

private val person: Person by bindArgument("person")
Copy the code

inspection

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.onClick {
            startActivity<SecondActivity>(
                    "id" to 9547,
                    "name" to "WG")}}}Copy the code
class SecondActivity : AppCompatActivity() {
    private val id: Int by bindExtra("id")
    private val name: String by bindExtra("name")
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        textView.text = "get from MainActivity id = ${id} name = ${name}"

        button2.onClick {
            val transition = supportFragmentManager.beginTransaction()
            val tfg = TFragment.newInstance(Person("Zhao Ritian"."Male", 24))
            transition.replace(R.id.fragment, tfg)
            transition.commit()
        }
    }
}
Copy the code
open class TFragment : Fragment() {
    private val person: Person by bindArgument("person")

    companion object {
        fun newInstance(p: Person): TFragment {
            val fg = TFragment()
            val bundle = Bundle()
            bundle.putParcelable("person", p)
            fg.arguments = bundle
            return fg} } override fun onCreateView(inflater: LayoutInflater? , container: ViewGroup? , savedInstanceState: Bundle?) : View? {returninflater? .inflate(R.layout.fragment_t, container,false) } override fun onViewCreated(view: View? , savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) textView2.text ="get from SecondActivity msg = ${person.toString()}"}}Copy the code

expand

As we all know, Anko can be said to be a Cool Android Kotlin expansion library, combined with Anko, can be very simple to achieve the Activity with the jump and access. Here, again, we write an Activity with a reference:

val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("id", 5)
startActivity(intent)
Copy the code

With Anko, we can do this in a single sentence, and we can use map to fill parameters:

startActivity<SecondActivity>(
                    "id" to 5,
                    "name" to "WG"
            )
Copy the code

For more details, visit Anko

Afterword.

As for Kotlin, I have been in contact with him for a short time and I am still in the process of learning. If any of the above descriptions are incorrect, please correct me and make progress together. BTW, click careful heart bai ~~~n(≧▽≦)n