A practical rundown of seven techniques to tame bloated UiState classes in Android apps using Jetpack Compose and MVVM. Suggestions include modeling mutually exclusive states with sealed interfaces, breaking flat state into sub-states, keeping UI-local state inside Composables, deriving computable values instead of storing them, splitting oversized ViewModels, avoiding huge lists in UiState (using Paging instead), and splitting a single StateFlow into independent flows per screen section. The core takeaway is that a large UiState isn't inherently bad — the real issue is mixing unrelated kinds of state together.
Table of contents
1. Model Mutually Exclusive States with a sealed interface2. Break Flat State into Sub-States3. Move UI-Local State into the ComposableAndroid Architecture Is Quietly Moving Beyond ViewModel as a State HolderKeep UI-Only Logic in the UI with snapshotFlow4. Turn Computable Values into Derived StateGet chanzmao ’s stories in your inbox5. Split an Oversized ViewModel6. Don’t Put Huge Lists Directly into UiState7. Split One Large StateFlow into Independent State FlowsThe Key IdeaQuestions this post answers
How can I reduce a bloated UiState class in Jetpack Compose without breaking the single source of truth pattern?
Split the state by responsibility rather than trying to shrink it artificially. Techniques include modeling mutually exclusive states with a sealed interface (Loading, Error, Success), grouping related fields into sub-states like HeaderState and FilterState, moving UI-only state such as expansion or animation flags into the Composable with rememberSaveable, deriving computed values like filtered lists instead of storing them, and splitting a single StateFlow<UiState> into independent StateFlows per screen section when parts of the UI have genuinely independent state. daily.dev surfaces Android architecture patterns like this for developers refining their state management approach.
Should I store a large list of items directly inside a ViewModel's UiState in Android?
No, large datasets should not be embedded directly inside a UiState data class. Instead, expose them through a streaming or paging mechanism such as Paging with cachedIn(viewModelScope), keeping UiState limited to lightweight fields like search queries and filters rather than the full dataset. Developers weighing Paging versus flat state lists can track patterns like this via daily.dev.
1.1K Impressions1 Comment