Skip to content

Instantly share code, notes, and snippets.

@jimmymorales
Created April 20, 2021 14:41
Show Gist options
  • Save jimmymorales/8c536b473eec4ea484368665205e6328 to your computer and use it in GitHub Desktop.
Save jimmymorales/8c536b473eec4ea484368665205e6328 to your computer and use it in GitHub Desktop.
MviViewModel with ViewEventProducer
package com.advancedrecoverysystems.nobu.mvi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.scan
import timber.log.Timber
interface ViewState
interface ViewIntent
interface ViewModelAction
interface ViewEvent
abstract class MviViewModel<
STATE : ViewState,
INTENT : ViewIntent,
ACTION : ViewModelAction,
EVENT : ViewEvent
>(
initialState: STATE,
private val viewEventProducer: ViewEventProducer<EVENT> = ViewEventProducerImpl()
) : ViewModel(), ViewEventProducer<EVENT> by viewEventProducer {
private val internalState = MutableStateFlow(initialState)
val state: StateFlow<STATE> = internalState.asStateFlow()
private val viewIntents = Channel<INTENT>(capacity = Channel.UNLIMITED)
private val viewModelActions = Channel<ACTION>(capacity = Channel.UNLIMITED)
init {
viewIntents.consumeAsFlow()
.onEach { intent ->
Timber.v("Processing intent = $intent")
handleIntent(intent)
}
.launchIn(viewModelScope)
@Suppress("EXPERIMENTAL_API_USAGE")
viewModelActions.consumeAsFlow()
.scan(initialState) { state, action ->
Timber.v("Reducing action = $action")
Timber.v("Old state = $state")
reduce(state, action)
}
.onEach { newState ->
internalState.value = newState
Timber.v("New state = $newState")
}
.launchIn(viewModelScope)
}
suspend fun onIntent(intent: INTENT) {
viewIntents.send(intent)
}
protected suspend fun onAction(action: ACTION) {
viewModelActions.send(action)
}
protected abstract suspend fun reduce(state: STATE, action: ACTION): STATE
protected abstract suspend fun handleIntent(intent: INTENT)
}
interface ViewEventProducer<EVENT : ViewEvent> {
val events: Flow<EVENT>
suspend fun triggerEvent(event: EVENT)
}
internal class ViewEventProducerImpl<EVENT : ViewEvent> : ViewEventProducer<EVENT> {
private val internalEvents = Channel<ConsumableEvent<EVENT>>(Channel.BUFFERED)
override val events: Flow<EVENT> = internalEvents.receiveAsFlow()
.mapNotNull { event -> event.getContentIfNotHandled() }
override suspend fun triggerEvent(event: EVENT) {
internalEvents.send(ConsumableEvent(event))
}
}
@jimmymorales
Copy link
Author

fun <STATE : ViewState, INTENT : ViewIntent, ACTION : ReduceAction, EVENT : ViewEvent> Fragment.subscribeToViewEvents(
    mviViewModel: MviViewModel<STATE, INTENT, ACTION, EVENT>,
    handleEvent: (EVENT) -> Unit,
) {
    viewLifecycleOwner.lifecycleScope.launchWhenCreated {
        mviViewModel.events
            .flowWithLifecycle(viewLifecycleOwner.lifecycle)
            .onEach { event -> handleEvent(event) }
            .launchIn(viewLifecycleOwner.lifecycleScope)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment