background

Cross-page communication is a common scenario, and we usually choose to use EventBus, but EventBus is not aware of the life cycle and calls back when it receives a message, so LiveData was soon followed by LiveEventBus. It does have drawbacks, such as the inability to switch the receiving thread. Now that SharedFlow is stable, can we do another wave?

Hence the FlowEventBus

Common message bus comparison

The message bus Delay to send Received message in order Sticky Life cycle perception Across processes/APP Thread distribution
EventBus
RxBus
LiveEventBus
FlowEventBus

Design concept

Learn how to migrate from LiveData to Kotlin data streams:

  • SharedFlow as event carrier:

Advantages:

  • Easily switch threads relying on coroutines
  • The stickiness effect can be achieved through replay
  • Can be subscribed to by multiple observers
  • Auto-clearing events without observers does not cause a backlog

Combined with Lifecycle perception of Lifecycle, the response time can be controlled.

Not only can the global scope of the event, but also a single page communication without being transmitted to other pages, such as: Activity internal communication, Fragment internal communication.

Dependent library version

The key is kotlinx-coroutines > 1.4.x and lifecycle Run-time KTX > 2.3.x

API

*Event in the following examples are arbitrary classes and are just names defined for the purpose of distinguishing events during the test

Event to send

// Global scope
postEvent(AppScopeEvent("form TestFragment"))

//Fragment Internal scope
postEvent(fragment,FragmentEvent("form TestFragment"))

//Activity internal scope
postEvent(requireActivity(),ActivityEvent("form TestFragment"))
Copy the code

Event listeners

// Receive the Activity Scope event
observeEvent<ActivityEvent>(scope = requireActivity()) {
    ...
}

// Receive the Fragment Scope event
observeEvent<FragmentEvent>(scope = fragment) {
    ...
}

// Receive the App Scope event
observeEvent<AppScopeEvent> {
    ...
}

Copy the code

Like ObserveForever:

// You need to specify the coroutine range
observeEvent<GlobalEvent>(scope = coroutineScope) {
       ...
}
Copy the code

Delay to send

postEvent(CustomEvent(value = "Hello Word"),1000)
Copy the code

thread

observeEvent<ActivityEvent>(Dispatchers.IO) {
    ...
}
Copy the code

Specifies the minimum perceived life state

observeEvent<ActivityEvent>(minActiveState = Lifecycle.State.DESTROYED) {
   ...
}
Copy the code

Listen in a sticky way

observeEvent<GlobalEvent>(isSticky = true) {... }Copy the code

Remove sticky events

 removeStickyEvent(StickyEvent::class.java)
 removeStickyEvent(fragment,StickyEvent::class.java)
 removeStickyEvent(activity,StickyEvent::class.java)
Copy the code

The principle of

This functionality relies on Kotlin coroutines for SharedFlow and Lifecycle and is therefore very simple to implement.

  • Viscous event
MutableSharedFlow<Any>(
    replay = if (isSticky) 1 else 0,
    extraBufferCapacity = Int.MAX_VALUE // Avoid data sending failure due to suspension
)
Copy the code
  • Life cycle perception
fun <T> LifecycleOwner.launchWhenStateAtLeast(
    minState: Lifecycle.State,
    block: suspend CoroutineScope. () - >T
) {
    lifecycleScope.launch {
        lifecycle.whenStateAtLeast(minState, block)
    }
}
Copy the code
  • Switch threads

WhenStateAtLeast executes blocks on the main thread by default, so you need to manually switch threads:

lifecycleOwner.launchWhenStateAtLeast(minState) {
    flow.collect { value ->
        lifecycleOwner.lifecycleScope.launch(dispatcher) {
                onReceived.invoke(value as T)
        }
    }
}
Copy the code
  • Delay events
viewModelScope.launch {
    delay(time)
    flow.emit(value)
}
Copy the code
  • In order to distribute

Flow itself is ordered

  • Global singleton

Use the global ViewModel, mainly because of the ViewModelScope, to avoid using GlobalScope, if you want a single page internal component communication, then use ActivityScope’s ViewModel:

object ApplicationScopeViewModelProvider : ViewModelStoreOwner {

    private val eventViewModelStore: ViewModelStore = ViewModelStore()

    override fun getViewModelStore(a): ViewModelStore {
        return eventViewModelStore
    }

    private val mApplicationProvider: ViewModelProvider by lazy {
        ViewModelProvider(
            ApplicationScopeViewModelProvider,
            ViewModelProvider.AndroidViewModelFactory.getInstance(EventBusInitializer.application)
        )
    }

    fun <T : ViewModel> getApplicationScopeViewModel(modelClass: Class<T>): T {
        return mApplicationProvider[modelClass]
    }
}
Copy the code

There are two maps inside the ViewModel, viscous and non-viscous:


internal class EventBusViewModel : ViewModel() {

    private val eventFlows: HashMap<String, MutableSharedFlow<Any>> = HashMap()
   
    private val stickyEventFlows: HashMap<String, MutableSharedFlow<Any>> = HashMap()
    ...

}
Copy the code

conclusion

Standing on the shoulders of giants can also be a simple understanding of the principle. Do not pass quite complex, need to next bit of effort 😄

kotlinx.coroutines.flow

Attached to Github: github.com/biubiuqiu0/…