This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| sealed class MainViewEvent { | |
| data class NewsItemClicked(val newsItem: NewsItem) : MainViewEvent() | |
| object FabClicked : MainViewEvent() | |
| object OnSwipeRefresh : MainViewEvent() | |
| object FetchNews : MainViewEvent() | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| open class AacMviViewModelDH<STATE, EFFECT, EVENT>(application: Application, private val aacMviDH: AacMviDH<STATE, EFFECT>) : | |
| AndroidViewModel(application), ViewModelContract<EVENT> { | |
| fun viewStates(): LiveData<STATE> = aacMviDH.stateLiveData | |
| fun viewEffects(): LiveData<EFFECT> = aacMviDH.effectLiveData | |
| @CallSuper | |
| override fun process(viewEvent: EVENT) { | |
| Log.d(TAG, "processing viewEvent : $viewEvent") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| open class AacMviDH<STATE, EFFECT> { | |
| private val _states: MutableLiveData<STATE> = MutableLiveData() | |
| val stateLiveData: LiveData<STATE> | |
| get() = _states | |
| private var _state: STATE? = null | |
| var state: STATE | |
| get() = _state | |
| ?: throw UninitializedPropertyAccessException("\"state\" was queried before being initialized") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Prerequisites: This builds on our foundation: Best Architecture For Android : MVI + LiveData + ViewModel = ❤️. The core concepts of Unidirectional Data Flow, ViewState, ViewEffect, and ViewEvent remain intact. | |
| The original architecture served us well, but the Android ecosystem has shifted. LiveData is tied to the Android SDK and struggles with one-shot events. SingleLiveEvent was a hack. Jetpack Compose completely changed the UI rendering contract. | |
| Here is how we evolved our MVI base to use Kotlin Flows, support Jetpack Compose, and guarantee lifecycle safety. | |
| 1. State: LiveData ➡️ StateFlow | |
| LiveData binds lifecycle-awareness into the ViewModel. StateFlow pushes that responsibility to the UI layer, keeping the ViewModel pure Kotlin. It always holds a value and replays it instantly to new collectors, making it the perfect replacement for ViewState. | |
| // In AacMviViewModel.kt | |
| private val _viewStates: MutableStateFlow<STATE> by lazy { MutableStateFlow(viewState) } | |
| override val viewStates: StateFlow<STATE> get() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Prerequisites: This builds on our modernised MVI foundation: Evolving Android MVI: Kotlin Flows, Compose, and Lifecycle Safety. | |
| MVI keeps screen state predictable, but it creates a new problem: Fat ViewModels. | |
| If your app has a complex "Feature Carousel" component used on the Home Screen, the Search Screen, and the Profile Screen, duplicating the loading, error handling, and state logic across three ViewModels violates DRY. You cannot use inheritance because a ViewModel can only extend one base class, and a carousel is just one piece of a screen. | |
| We need Composition. We need to extract the MVI logic of that component into a reusable unit that any ViewModel can plug in. | |
| Introducing AacMviDelegateVM | |
| A DelegateVM is a plain class (not an AndroidViewModel) that encapsulates a full MVI loop (ViewState, ViewEffect, ViewEvent) for a specific, reusable UI component. | |
| It shares code, not state. Each Host ViewModel instantiates its own unique instance of the Delegate. The Host ViewModel funnels the Delegate's state and |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| Replacing LocalBroadcastManager in Android: SharedFlow + Sealed Classes = ❤️ | |
| A modern, lifecycle-safe, type-driven in-app event bus using Kotlin Coroutines and SharedFlow - demonstrated with a News Reader app. | |
| TL;DR - If you are already familiar with Kotlin Coroutines, SharedFlow, and sealed classes, skip the basics and jump straight to the LocalBroadcast + BroadcastEvent section. | |
| Preface | |
| Every non-trivial Android app eventually runs into the same problem: how do you communicate between two completely unrelated parts of your app - a background sync service and a fragment, a repository and a ViewModel - without coupling them together? | |
| For years, the go-to answer was LocalBroadcastManager. It was simple. It worked. And then Google deprecated it. | |
| "LocalBroadcastManager is an application-wide event bus and embraces layer violations in your app; any component may listen to events from any other component… Use other means of communicating between components." | |
| - Android Developer Documentation |
OlderNewer