State
UI
Action
(ViewModel)
(Activity/Fragment)
User
Intent
Model
View
Reducer
State
Data source
Intent
Transformer
View
Model
by Hannes Dorfmann
class PostListViewModel : ViewModel() {
private val _state = MutableStateFlow(PostListState())
val state: StateFlow<PostListState> = _state
}
data class PostOverview(
val id: Int,
val title: String,
val author: String
)
data class PostListState(
val overviews: List<PostOverview> = emptyList()
)
class PostListViewModel : ViewModel() {
private val _state = MutableStateFlow(PostListState())
val state: StateFlow<PostListState> = _state
fun loadPosts() {
viewModelScope.launch {
val posts = repo.getPosts()
_state.value = _state.value.copy(overviews = posts)
}
}
}
class PostListViewModel : ViewModel() {
private val _state = MutableStateFlow(PostListState())
val state: StateFlow<PostListState> = _state
fun loadPosts() {
viewModelScope.launch(SINGLE_THREAD) {
val posts = repo.getPosts()
withContext(SINGLE_THREAD) {
_state.value = _state.value.copy(overviews = posts)
}
}
}
companion object {
private val SINGLE_THREAD = newSingleThreadContext("mvi")
}
}
class PostListViewModel : ViewModel() {
private val _state = MutableStateFlow(PostListState())
val state: StateFlow<PostListState> = _state
fun loadPosts() {
viewModelScope.launch(SINGLE_THREAD) {
val posts = repo.getPosts()
reduce {
copy(overviews = posts)
}
}
}
private suspend fun reduce(reducer: PostListState.() -> PostListState) =
withContext(SINGLE_THREAD) {
_state.value = _state.value.reducer()
}
companion object {
private val SINGLE_THREAD = newSingleThreadContext("mvi")
}
}
class PostListViewModel : ViewModel() {
...
fun viewDetails(postId: Int) {
viewModelScope.launch(SINGLE_THREAD) {
// Navigate to post details
}
}
...
}
data class PostListState(
val overviews: List<PostOverview> = emptyList(),
val navigateToPost: Int? = null
)
class PostListViewModel : ViewModel() {
private val _sideEffect = Channel<NavigationEvent>(Channel.BUFFERED)
val sideEffect: Flow<NavigationEvent> = _sideEffect.receiveAsFlow()
fun viewDetails(postId: Int) {
viewModelScope.launch(SINGLE_THREAD) {
_sideEffect.send(OpenPostNavigationEvent(postId))
}
}
...
}
class PostListViewModel : ViewModel() {
private val _sideEffect = Channel<NavigationEvent>(Channel.BUFFERED)
val sideEffect: Flow<NavigationEvent> = _sideEffect.receiveAsFlow()
fun viewDetails(postId: Int) {
viewModelScope.launch(SINGLE_THREAD) {
postSideEffect(OpenPostNavigationEvent(postId))
}
}
private suspend fun postSideEffect(event: NavigationEvent) =
_sideEffect.send(event)
...
}
class PostListViewModel : ViewModel() {
...
fun loadPosts() = intent {
val posts = repo.getPosts()
reduce {
_state.value.copy(overviews = posts)
}
}
fun viewDetails(postId: Int) = intent {
postSideEffect(OpenPostNavigationEvent(postId))
}
private fun intent(transform: suspend PostListViewModel.() -> Unit) =
viewModelScope.launch(SINGLE_THREAD) {
this@PostListViewModel.transform()
}
...
}
class PostListViewModel : ViewModel() {
private val _state = MutableStateFlow(PostListState())
val state: StateFlow<PostListState> = _state
private val _sideEffect = Channel<NavigationEvent>(Channel.BUFFERED)
val sideEffect: Flow<NavigationEvent> = _sideEffect.receiveAsFlow()
private fun intent(transform: suspend PostListViewModel.() -> Unit) =
viewModelScope.launch(SINGLE_THREAD) {
this@PostListViewModel.transform()
}
private suspend fun reduce(reducer: PostListState.() -> PostListState) =
withContext(SINGLE_THREAD) {
_state.value = _state.value.reducer()
}
private suspend fun postSideEffect(event: NavigationEvent) =
_sideEffect.send(event)
companion object {
private val SINGLE_THREAD = newSingleThreadContext("mvi")
}
...
class Container(
private val scope: CoroutineScope
) {
private val _state = MutableStateFlow(PostListState())
val state: StateFlow<PostListState> = _state
private val _sideEffect = Channel<NavigationEvent>(Channel.BUFFERED)
val sideEffect: Flow<NavigationEvent> = _sideEffect.receiveAsFlow()
fun intent(transform: suspend Container.() -> Unit) =
scope.launch(SINGLE_THREAD) {
this@Container.transform()
}
suspend fun reduce(reducer: PostListState.() -> PostListState) =
withContext(SINGLE_THREAD) {
_state.value = _state.value.reducer()
}
suspend fun postSideEffect(event: NavigationEvent) =
_sideEffect.send(event)
...
}
class Container<STATE, SIDE_EFFECT>(
private val scope: CoroutineScope,
initialState: STATE
) {
private val _state = MutableStateFlow(initialState)
val state: StateFlow<STATE> = _state
private val _sideEffect = Channel<SIDE_EFFECT>(Channel.BUFFERED)
val sideEffect: Flow<SIDE_EFFECT> = _sideEffect.receiveAsFlow()
fun intent(transform: suspend Container<STATE, SIDE_EFFECT>.() -> Unit) =
scope.launch(SINGLE_THREAD) {
this@Container.transform()
}
suspend fun reduce(reducer: STATE.() -> STATE) =
withContext(SINGLE_THREAD) {
_state.value = _state.value.reducer()
}
suspend fun postSideEffect(event: SIDE_EFFECT) =
_sideEffect.send(event)
...
}
class PostListViewModel : ViewModel() {
val container = Container<PostListState, NavigationEvent>(
viewModelScope,
PostListState()
)
fun loadPosts() = container.intent {
val posts = repo.getPosts()
reduce {
copy(overviews = posts)
}
}
fun viewDetails(post: PostOverview) = container.intent {
postSideEffect(OpenPostNavigationEvent(post))
}
}
Intent
Intent
Intent
Intent
Stream
Dispatcher
Trans
former
Trans
former
Trans
former
Intent
Intent
Intent
Trans
former
Trans
former
Trans
former
class PostListState(
val overviews: List<PostOverview> = emptyList,
val navigateToPostId: Int?
)
class PostListViewModel {
fun onPostClicked(postOverview: PostOverview) {
viewModelScope.launch {
reduce {
copy(navigateToPostId = postOverview.id)
}
}
}
fun clearPostIdNavigation() {
viewModelScope.launch {
reduce {
copy(navigateToPostId = null)
}
}
}
}
class PostListFragment {
private fun navigateToDetails(postId: Int) {
viewModel.clearPostIdNavigation()
// Navigation code
}
}
class Container<STATE, SIDE_EFFECT>(
private val scope: CoroutineScope,
initialState: STATE
) {
private val _stateFlow = MutableStateFlow(initialState)
val state: StateFlow<STATE> = _stateFlow
private val _sideEffect = Channel<SIDE_EFFECT>(Channel.BUFFERED)
val sideEffect: Flow<SIDE_EFFECT> = _sideEffect.receiveAsFlow()
fun intent(transform: suspend Container<STATE, SIDE_EFFECT>.() -> Unit) =
scope.launch(SINGLE_THREAD) {
this@Container.transform()
}
suspend fun reduce(reducer: STATE.() -> STATE) =
withContext(SINGLE_THREAD) {
_stateFlow.value = _stateFlow.value.reducer()
}
suspend fun postSideEffect(event: SIDE_EFFECT) =
_sideEffect.send(event)
companion object {
private val SINGLE_THREAD = newSingleThreadContext("mvi")
}
}