Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)


preface

  • Delegates are a language feature of Kotlin for implementing Delegate patterns more elegantly;
  • In this article, I will summarize how Kotlin delegate is used and how it works. If you can help, please be sure to like and follow it. It’s really important to me.
  • The code for this article can be downloaded from DemoHall·KotlinDelegate.

directory


1. An overview of the

  • What is a delegate: One object delegates a message to another object for processing.
  • What Kotlin delegates solve: Kotlin can implement delegates more elegantly with the by keyword.

2. Kotlin delegate foundation

  • Class delegate: Methods of a class are not defined in that class, but are directly delegated to another object to handle.
  • Attribute delegate: Attributes of a class are not defined in that class, but are directly delegated to another object to handle.
  • Local variable delegate: A local variable is not defined in this method, but is delegated directly to another object to handle.

2.1 class delegate

The Kotlin class delegate syntax is as follows:

Class < class name >(b: < base interface >) : < base interface >Copy the code

For example:

// BaseImpl(val x: Int) : Base {override fun print() {print(x)}} // Class Derived(b: Base) : Base by b fun main(args: Array<String>) {val b = BaseImpl(10) Derived(b).print()Copy the code

Both the Base class and the delegated class implement the same interface, and in the bytecode generated at compile time, methods inherited from the Base interface are delegated to the Base object.

2.2 Attribute Delegation

The syntax for the Kotlin attribute delegate is as follows:

Val /var < attribute name > : < type > by < base object >Copy the code

For example:

Class Delegate {private var _realValue: String Delegate(); private var _realValue: String Delegate(); String = "peng" operator fun getValue(thisRef: Any? , property: KProperty<*>): String { println("getValue") return _realValue } operator fun setValue(thisRef: Any? , property: KProperty<*>, value: String) { println("setValue") _realValue = value } } fun main(args: Array<String>) {val e = Example() println(e.prop) // Final call Delegate#getValue() e.prop = "Peng Delegate#setValue() println(e.prop) // Finally call Delegate#getValue()} outputCopy the code

The base class does not need to implement any interface, but must provide getValue() methods and, if it is a delegate mutable property, setValue(). Behind each implementation of a property delegate, the Kotlin compiler generates and delegates a secondary property to it. For example, for the property prop, a “secondary property” prop$delegate is generated. The getter() and setter() methods of prop are simply delegated to the getValue() and setValue() handling of secondary properties.

Class Example {// Delegate attribute var prop: Based object String by the Delegate () / /} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the compiler to generate the bytecode: Class Example {private $delegate = delegate (); String get() = prop$delegate.getValue(this, this:prop) set(value : String) = prop$delegate.setValue(this, this:prop, value) }Copy the code

Matters needing attention:

  • ThisRef – must be the same as the property owner type or its supertype.
  • Property — Must be type KProperty<*> or its supertype.
  • Value – must be of the same type as the property or its supertype.

2.3 Local variable delegate

Local variables can also declare delegates, for example:

fun main(args: Array<String>) { val lazyValue: String by lazy { println("Lazy Init Completed!" ) "Hello World."} if (true/*someCondition*/) {println(lazyValue) // first call println(lazyValue) // later call}}  Lazy Init Completed! Hello World. Hello World.Copy the code

3. Kotlin delegate progression

3.1 The delay attribute delegates lazy

Lazy is a library function that takes a Lambda expression and returns an instance of lazy. Lazy can be used to implement lazy attribute delegation, which is useful in resource-intensive delegate scenarios. The first access to the property is a lambda expression that executes the lazy function and logs the result to the “back field,” and subsequent calls to the getter() method simply return the “back field” value directly. Such as:

val lazyValue: String by lazy { println("Lazy Init Completed!" ) "Hello World." } fun main(args: Array<String>) {println(lazyValue) // First call println(lazyValue) // subsequent calls} Output: Lazy Init Completed! Hello World. Hello World.Copy the code

3.2 ObservableProperty

The observable property can be achieved using Delegates.Observable (). The function accepts two parameters: the first parameter is the initial value and the second parameter is the callback for the change of the property value. The return value of the function is the ObservableProperty, which calls setValue(…). Trigger callback. Such as:

class User { var name: {if ($old -> $new)}} Fun main(args: $Delegates: {if ($old -> $new)}}... Array<String>) {val user = user () user.name = "first assignment" user.name = "second assignment"} Output: Old value: initial value -> new value: first assignment Old value: first assignment -> New value: second assignmentCopy the code

3.3 Using Map to Store Attribute Values

Map/MutableMap can also be used to implement property delegates where the field name is Key and the property Value is Value. For example;

class User(val map: Map<String, Any? >) { val name: String by map } fun main(args: Array<String>) {val map = mutableMapOf("name" to "peng") val user = user (map) println(user.name) map["name"] = "peng" Println (user.name)} Output: peng pengCopy the code

$Key is missing in the Map. $Key is missing in the Map. The source code is as follows:

The standard library · MapAccessors. Kt

@kotlin.jvm.JvmName("getVar")
@kotlin.internal.InlineOnly
public inline operator fun <V, V1 : V> MutableMap<in String, out @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1 = (getOrImplicitDefault(property.name) as V1)

@kotlin.internal.InlineOnly
public inline operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
    this.put(property.name, value)
}
Copy the code

The standard library · MapWithDefault. Kt

@kotlin.jvm.JvmName("getOrImplicitDefaultNullable")
@PublishedApi
internal fun <K, V> Map<K, V>.getOrImplicitDefault(key: K): V {
    if (this is MapWithDefault)
        return this.getOrImplicitDefault(key)

    return getOrElseNullable(key, { throw NoSuchElementException("Key $key is missing in the map.") })
}
Copy the code

I can’t figure out why Kotlin put this restriction in place, so INSTEAD of using the standard library implementation, I used the following custom implementation:

MapAccessors.kt

class MapAccessors(val map: MutableMap<String, Any? >) { public inline operator fun <V> getValue(thisRef: Any? , property: KProperty<*>): V = @Suppress("UNCHECKED_CAST") (map[property.name] as V) public inline operator fun <V> setValue(thisRef: Any?, property: KProperty<*>, value: V) {map[property.name] = value}} // use MapAccessors () : {map[property.name] = value}}  private val _data = MapAccessors(HashMap<String, Any?>()) private var count: Int? by _dataCopy the code

3.4 ReadOnlyProperty/ReadWriteProperty

When implementing a property Delegate or local Delegate, in addition to defining a class Delegate, you can directly use two interfaces from the Kotlin library: ReadOnlyProperty/ReadWriteProperty. Using ReadOnlyProperty for val variables and ReadWriteProperty for var variables makes it easy for the IDE to generate function signatures for you. Such as:

val name by object : ReadOnlyProperty<Any? , String> { override fun getValue(thisRef: Any? , property: KProperty<*>): String { return "Peng" } } var name by object : ReadWriteProperty<Any? , String> { override fun getValue(thisRef: Any? , property: KProperty<*>): String { return "Peng" } override fun setValue(thisRef: Any? , property: KProperty<*>, value: String) { } }Copy the code

4. Use Kotlin delegate in Android

4.1 Kotlin delegate + Fragment/Activity Parameter Passing

We often need to pass parameters between activities/fragments like this:

OrderDetailFragment.kt

class OrderDetailFragment : Fragment(R.layout.fragment_order_detail) { private var orderId: Int? = null private var orderType: Int? = null companion object { const val EXTRA_ORDER_ID = "orderId" const val EXTRA_ORDER_TYPE = "orderType"; fun newInstance(orderId: Int, orderType: Int?) = OrderDetailFragment().apply { Bundle().apply { putInt(EXTRA_ORDER_ID, orderId) if (null ! = orderType) { putInt(EXTRA_ORDER_TYPE, orderType) } }.also { arguments = it } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments? .let { orderId = it.getInt(EXTRA_ORDER_ID, 10000) orderType = it.getInt(EXTRA_ORDER_TYPE, 2) } } }Copy the code

As you can see, we have to write similar template code for each parameter, and we have to worry about the argument being null, and we can’t use the lateinit keyword for the Int base type, and you have to declare the attribute as nullable even though you can make sure it’s not null.

Is there a way to converge the template code? This is where the delegate mechanism fits in. We can extract the delegate class from the parameter assignment and fetch code, and then declare orderId and orderType as “delegate properties.” Such as:

OrderDetailFragment.kt

class OrderDetailFragment : Fragment(R.layout.fragment_order_detail) {

    private lateinit var tvDisplay: TextView

    private var orderId: Int by argument()
    private var orderType: Int by argument(2)

    companion object {
        fun newInstance(orderId: Int, orderType: Int) = OrderDetailFragment().apply {
            this.orderId = orderId
            this.orderType = orderType
        }
    }

    override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
        // Try to modify (UnExcepted)
        this.orderType = 3
        // Display Value
        tvDisplay = root.findViewById(R.id.tv_display)
        tvDisplay.text = "orderId = $orderId, orderType = $orderType"
    }
}
Copy the code

Clean and fresh! There are obvious advantages to using attribute delegate over conventional writing:

  • 1. Reduced boilerplate code: you no longer need to define Key strings, but instead use variable names as keys; You no longer need to write code to set and read arguments to Argument;
  • Non-empty parameters can be declared as val: nullable and non-empty parameters distinguish two kinds of delegates. Now non-empty parameters can also be declared as val.
  • 3, clearly set the default value of nullable parameters: when declaring nullable parameters, you can also declare the default value.

In addition to Fragment parameters, Activity parameters can also be passed using delegate attributes. Complete code and demo project you can directly download view: download path, here only show part of the core code as follows:

ArgumentDelegate.kt

fun <T> fragmentArgument() = FragmentArgumentProperty<T>() class FragmentArgumentProperty<T> : ReadWriteProperty<Fragment, T> { override fun getValue(thisRef: Fragment, property: KProperty<*>): T { return thisRef.arguments? .getValue(property.name) as? T ?: throw IllegalStateException("Property ${property.name} could not be read") } override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { val arguments = thisRef.arguments ?: Bundle().also { thisRef.arguments = it } if (arguments.containsKey(property.name)) { // The Value is not expected to be modified return } arguments[property.name] = value } }Copy the code

4.2 Kotlin delegate + ViewBinding

ViewBinding is a new feature in Android Gradle Plugin 3.6 that is used to implement ViewBinding more lightly, and can be understood as a lightweight version of DataBinding. The usage and implementation of ViewBinding are well understood, but there are some limitations to the general usage:

  • 1. Creating and recycling ViewBinding objects requires repetitive boilerplate code, especially in Fragment cases;
  • 2. The Binding attribute is nullable and mutable, making it difficult to use.

Using the Kotlin attribute delegate can solve these two problems very elegantly, optimizing before and after:

TestFragment.kt

class TestFragment : Fragment(R.layout.fragment_test) {

    private var _binding: FragmentTestBinding? = null

    private val binding get() = _binding!!

    override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
        _binding = FragmentTestBinding.bind(root)

        binding.tvDisplay.text = "Hello World."
    }

    override fun onDestroyView() {
        super.onDestroyView()

        _binding = null
    }
}
Copy the code

After the optimization:

TestFragment.kt

class TestFragment : Fragment(R.layout.fragment_test) {

    private val binding by viewBinding(FragmentTestBinding::bind)

    override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
        binding.tvDisplay.text = "Hello World."
    }
}
Copy the code

Clean and fresh! Detailed analysis of the process you read my another article directly: Android | ViewBinding and Kotlin entrust shuangjian combination


5. To summarize

The Kotlin delegate’s syntactic key is BY, which is essentially a compiler-oriented syntactic sugar, and all three kinds of delegates (class delegates, object delegates, and local variable delegates) are converted to “sugar-free syntax” at compile time. For example, class delegation: The compiler implements all the methods of the underlying interface and delegates them directly to the underlying object. Examples are object delegates and local variable delegates: auxiliary properties (prop$degelate) are generated at compile time, while getter() and setter() methods for properties/variables are simply delegated to the getValue() and setValue() handling of auxiliary properties.


The resources

  • Commissioned by Kotlin — Tutorial for Beginners
  • Kotlin combat (chapter 7). By DmitryJeme

Creation is not easy, your “three lian” is chouchou’s biggest motivation, we will see you next time!