background

The little red dot can be seen everywhere in each App, and with the continuous iteration of requirements, there are more and more requirements to display the little red dot.

  • The red dots indicate possible conflicts between different requirements.
  • The red dots will be associated with different pages.
  • The same red dot may appear as a number style, a red dot style, a copywriting style.

At this point, if there is no unified abstraction and management of the red dot presentation logic, it will feel complicated and not easy to maintain later.

This paper will be based on MediatorLiveData to achieve unified management of red dots.

Demand analysis

Here is an example of the common red dot scene, similar to the RED dot in the upper left corner of THE QQ home page.

  1. Four pages, from the home page to the privacy protection guide page, all have corresponding red dot View display, to guide users to enter the innermost “privacy protection guide” page.
  2. When the user clicks the red dot to enter the page of “Privacy Protection Guidelines”, the red dot corresponding to the privacy protection guidelines will disappear and trigger the red dot refresh of the superior page.




Thought analysis

The tree model

The page of an App itself is hierarchical, and the access path of the page is essentially a tree structure. The whole idea is to use the tree model to manage the red dot of different pages.

  • Each red dot is a node of the tree, and whether the red dot of the parent node is displayed depends on the result of the union of its children.
  • Different red dots on the same page. It’s the same hierarchy in the tree, it’s brothers, independent of each other.
  • The state change of the child node recursively triggers the state change of the parent node.

Concrete code implementation

How should the corresponding code be implemented? Do you really need to implement a tree by hand? It’s not like I can’t. I just feel like I’m in a little bit of trouble. Let’s get down to business.

MediatorLiveData

MediatorLiveData is officially provided.

  • With the addSource method, you can listen for data changes in another LiveData
  • Is itself a LiveData that can be observed by other observers

These two features just meet our needs to achieve. For example, MediatorLiveData A observes MediatorLiveData B, and MediatorLiveData B observes MediatorLiveData C and MediatorLiveData D. In addition, the observed LiveData will be notified when changes occur.

By managing the relationships between multiple LiveData, a tree model can be implemented indirectly.

public class MediatorLiveData<T> extends MutableLiveData<T> { private SafeIterableMap<LiveData<? >, Source<? >> mSources = new SafeIterableMap<>(); public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? Super S> onChanged) {Source<S> e = new Source<>(Source, onChanged); Source<? > existing = mSources.putIfAbsent(source, e); if (hasActiveObservers()) { e.plug(); } } private static class Source<V> implements Observer<V> { final LiveData<V> mLiveData; final Observer<? super V> mObserver; int mVersion = START_VERSION; Source(LiveData<V> liveData, final Observer<? super V> observer) { mLiveData = liveData; mObserver = observer; } void plug() { mLiveData.observeForever(this); } void unplug() { mLiveData.removeObserver(this); } @Override public void onChanged(@Nullable V v) { if (mVersion ! = mLiveData.getVersion()) { mVersion = mLiveData.getVersion(); mObserver.onChanged(v); }}}}Copy the code

RedPointManager

  • The implementation here, encapsulated as a singleton RedPointManager, exposes the corresponding red dot data to the outside world.
  • LiveData data-driven: RedPointManager contains multiple LiveData, and red dot views on different pages can drive their own View changes by observing the corresponding LiveData.
  • The parent node uses MediatorLiveData to observe the corresponding child node LiveData. The leaf node is defined as plain LiveData, since no other objects need to be observed.

/** * Class RedPointManager: IRedPointManager { companion object { val TAG = "RedPointManager" @JvmStatic val instance: IRedPointManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { RedPointManager() } } override val liveDataA = MediatorLiveData<Boolean>() override val liveDataB1 = MediatorLiveData<Boolean>() override val liveDataB2 = MutableLiveData<Boolean>() override val liveDataC1 = MutableLiveData<Boolean>() override val liveDataC2 = MutableLiveData<Boolean>() init {log. d(TAG, "RedPointManager init") /** * Observe at the path level. Generally, the external only needs to change the LiveData corresponding to the red dot at the lowest layer. The top LiveData will automatically change */ liveDataa.addSource (liveDataB1, Observer { liveDataA.postValue(liveDataB1.isTrue() || liveDataB2.isTrue()) }) liveDataA.addSource(liveDataB2, Observer { liveDataA.postValue(liveDataB1.isTrue() || liveDataB2.isTrue()) }) liveDataB1.addSource(liveDataC1, Observer { liveDataB1.postValue(liveDataC1.isTrue() || liveDataC2.isTrue()) }) liveDataB1.addSource(liveDataC2, Observer { liveDataB1.postValue(liveDataC1.isTrue() || liveDataC2.isTrue()) }) } override fun testChangeDataC1(show: Boolean) { liveDataC1.postValue(show) Log.d(TAG, "testChangeDataC1: $show")}} / / IRedPointManager {val liveDataA: LiveData<Boolean> val liveDataB1: LiveData<Boolean> val liveDataB2: LiveData<Boolean> val liveDataC1: LiveData<Boolean> val liveDataC2: LiveData<Boolean> fun testChangeDataC1(show: Boolean) }Copy the code

Validate refresh logic

In general, you only need to change the LiveData corresponding to the red dot of the leaf node, and the LiveData of the parent node will be automatically changed. Based on the code above, after calling the testChangeDataC1 method, listen to LiveData and output the log.

private fun testRedPointManager() { RedPointManager.instance.liveDataA.observe(this, Observer { Log.d(TAG, "liveDataA: $it") }) RedPointManager.instance.liveDataB1.observe(this, Observer { Log.d(TAG, "liveDataB1: $it") }) RedPointManager.instance.liveDataB2.observe(this, Observer { Log.d(TAG, "liveDataB2: $it") }) RedPointManager.instance.liveDataC1.observe(this, Observer { Log.d(TAG, "liveDataC1: $it") }) RedPointManager.instance.liveDataC2.observe(this, Observer { Log.d(TAG, "liveDataC2: $it ")}) RedPointManager. Instance. TestChangeDataC1 (true)} / / from the output log can be found that the underlying liveDataC1 change, triggered the top liveDataB1 change. Changes to liveDataB1 also trigger changes to liveDataA. RedPointManager init testChangeDataC1: true liveDataC1: true lveDataB1: true liveDataA: trueCopy the code

conclusion

This is the end of the App end to achieve unified management of red dots, if there is a similar scenario, you can use this idea to achieve. The above example is relatively simple, and more complex scenarios can be modified based on the above scheme.

Reference article:

  • Little red dot solution analysis
  • Manage App numbers and red dot tips with a tree model