LiveData Stickiness data customization

  • Key words: Kotlin, Java, Hook, reflection, function default parameters, data stickiness

  • Abstract:

    • The data maintained by LiveData is sticky (the previous data can still be received after modification and subscription). In this example, Kotlin and custom hook function are used to dynamically modify LiveData source code by titol reflection mechanism to realize LiveData with switch (the program monkey decides whether to start stickiness)
  • Engineering structure:

  • Sticky data application scenarios: Use MutableLiveData if historical data is needed, but sticky data usually needs to be removed (which can cause problems)

Code diagram:

  • OkLiveDataBusKT

    • Implementation details:

      1. Overview:

        • Singleton mode remove stickiness event (with switch off stickiness) KT version

          Depending on the default function parameters, the user can specify whether to remove sticky data

      1. Why singletons

          object OKLiveDataBusKT {
        Copy the code
        • LiveData is often used as a utility class, where the code is pulled away to make the project structure clear
      1. Why encapsulate your own bus: Maintain a Map

           private val bus : MutableMap<String, BusMutableLiveData<Any>> by lazy { HashMap() }
        Copy the code
        • When using LiveData, generally use the modified LiveData through the decorator mode –>MutableLiveData(subclass of LiveData, internal only provide setValue and postValue)

          • Benefits: Simplified LiveData, chaser code, easy to use

          • Disadvantages:

            1. Use MutableLiveData to store subscribers, and encapsulate bus maintenance data by itself.
            2. In other words, a MutableLiveData can only maintain one piece of data; If you need to maintain multiple pieces of data, you need to introduce a MutableMap;
            3. And restricted by generics, you need to pass in a generic of the data to be maintained;
        • Why use Map: Can you maintain multiple types of data

          • Key: Indicates the data to be maintained

            • Mylivedata.info. observer(this,{it (refers to data to be maintained) : mylivedata.info. observer(this,{it (refers to data to be maintained)

              }

          • Value: stores the data to be maintained

          • Value < Any > : Why maintain Any

            • Secondary encapsulation of MutableLiveData When maintaining the original MutableLiveData data, you need to pass in the data type to be maintained. Because you are maintaining a set of data and the type of each data is uncertain, use Any
        • Why lazy loading is used:

          • This is a feature in Kotlin that is loaded on demand to save resources
        • Why lazy loading is a HashMap:

          • This Map is essentially a secondary encapsulation of the HashMap
      2. Why secondary encapsulation of MutableLiveData:

         class BusMutableLiveData<T> private constructor() : MutableLiveData<T>() {
        Copy the code
        • We want to implement a MutableLiveData with a data stickiness switch
      3. Why pass in two generics and inherit MutableLiveData:

        • We still use MutableLiveData. The difference is that we put a set of MutableLiveData (possibly of different types) that we maintain into a HashMap. And if the current generics are defined, the ones that follow will change
        • Inherit MutableLiveData: the ability to make data maintainable by LiveData, or to implement myLiveData.info. observer(this,{it})
      4. Why privatize constructors:

        • I’m still going to make sure that it’s global singleton, that you can’t get outside of your code, that you can only initialize it in this tool singleton
      5. Why display a subconstructor that writes this class

        Constructor (constructor: Boolean) : this() {this.isstick = isStick}Copy the code
        • Any function has a primary constructor (the default), but when this constructor is specified, the primary constructor needs to be called in the secondary constructor
      6. Why override the observe method

         override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        Copy the code
        • Overwrite the system source code and execute the overloaded function first when calling System Observe
      7. Why do I need to modify the LiveData source code dynamically

         private fun hook(observer: Observer<in T>) {
        Copy the code
        • Use reflection to dynamically modify LiveData source code to remove data stickiness: Make LiveData maintained in the observer of the data version number is equal to its version number in the observer core code: is to execute this if statement, remove stickiness,

            if (observer.mLastVersion >= mVersion) {
                        return;
                    }
                    observer.mLastVersion = mVersion;
                    //noinspection unchecked
                    observer.mObserver.onChanged((T) mData);
          Copy the code
        • Inheritance relationship:

          BusMutableLiveData– MutableLiveData– LiveData

  • MainActivity

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) setContentView(r.layout.activity_main) // If LiveData is sticky by default, bugs will be generated. Oklivedatabuskt. with("data1", String::class.java,false). Value = "Sticky data enabled, Kotlin version "OKLiveDataBusJava getInstance () with (" data2", String: : class. Java). The value = "viscosity data, Java version" / / / / Java version click event, Jump to the next Activity val button = the findViewById < button > (R.i db utton) button. SetOnClickListener {startActivity (Intent (this, MainActivity2::class.java)) } } }Copy the code
  • MainActivity2

    package com.derry.livedatabusandviewbinding.simple1 import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.derry.livedatabusandviewbinding.R import  kotlin.concurrent.thread class MainActivity2 : AppCompatActivity() { override fun onCreate(savedInstanceState: Super.oncreate (savedInstanceState) setContentView(r.laiout.activity_main2) // Kotlin version subscription observer, This key acts as a unique identifier for the data maintained in LiveData, Oklivedatabuskt. with("data1", String::class.java).observe(this, {toast. makeText(this, "Kotlin version of the observer :${it}", Toast.length_short).show()}) // Java version subscription observer You increase switch) OKLiveDataBusJava. GetInstance (). With (" data2 ", String: : class. Java). Observe (this, {Toast. MakeText (this, Java version of the observer: "${it}", Toast. LENGTH_SHORT.) the show ()})}}Copy the code
  • OKLiveDataBusJava

    package com.derry.livedatabusandviewbinding.simple1; import androidx.annotation.NonNull; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; /** * The default singleton mode is to remove stickiness, Java version; */ public class OKLiveDataBusJava {private Map<String, BusMutableLiveData<Object>> bus; private static OKLiveDataBusJava liveDataBus = new OKLiveDataBusJava(); private OKLiveDataBusJava() { bus = new HashMap<>(); } public static OKLiveDataBusJava getInstance() { return liveDataBus; Public synchronized <T> BusMutableLiveData<T> with(String key, Class<T> type) {if (! bus.containsKey(key)) { bus.put(key, new BusMutableLiveData<>()); } return (BusMutableLiveData<T>) bus.get(key); } /*public <T> MutableLiveData<T> with(String target, Class<T> type) { if (! bus.containsKey(target)) { bus.put(target, new MutableLiveData<>()); } return (MutableLiveData<T>) bus.get(target); } public synchronized BusMutableLiveData<Object> with(String target) {return with(target, synchronized BusMutableLiveData) Object.class); }*/ public static class BusMutableLiveData<T> extends MutableLiveData<T> { @Override public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) { super.observe(owner, observer); // Break hook(observer); } private void hook(Observer<? super T> observer) { try { // TODO 1. Class<LiveData> liveDataClass = livedata.class; Field mObserversField = liveDataClass.getDeclaredField("mObservers"); mObserversField.setAccessible(true); Object mObserversObject = mobServersField.get (this); // Get the map object class <? > mObserversClass = mObserversObject.getClass(); / / get access to mObservers Object Method get = mObserversClass. GetDeclaredMethod (" get ", the Object. The class); get.setAccessible(true); InvokeEntry = get.invoke(mObserversObject, observer); ObserverWraper = null; if (invokeEntry ! = null && invokeEntry instanceof Map.Entry) { observerWraper = ((Map.Entry) invokeEntry).getValue(); } if (observerWraper == null) { throw new NullPointerException("observerWraper is null"); } // get the observerWraperr Class<? > supperClass = observerWraper.getClass().getSuperclass(); Field mLastVersion = supperClass.getDeclaredField("mLastVersion"); mLastVersion.setAccessible(true); / / TODO 2. Get mVersion Field mVersion = liveDataClass. GetDeclaredField (" mVersion "); mVersion.setAccessible(true); // TODO 3.mLastVersion=mVersion Object mVersionValue = mVersion.get(this); mLastVersion.set(observerWraper, mVersionValue); } catch (Exception e) { e.printStackTrace(); }}}}Copy the code