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/…