Skip to content

Feature Screen Pattern

Andrés Pedraza Míguez edited this page Dec 6, 2025 · 2 revisions

To maintain clean code and enable Jetpack Compose @Preview, we separate the "Logic" from the "UI".

The Component: Screen

  • Responsibility: Pure UI rendering.
  • Dependencies: None. It relies on simple data classes or primitives.
  • State: Stateless (receives state as parameters).
  • Events: Exposes lambdas (e.g., onClick: () -> Unit).
  • Benefit: Can be previewed easily in Android Studio with dummy data.
@Composable
fun DefaultCurrencyScreen(
    state: CurrencyUiState, // Pure data
    onSelect: (String) -> Unit // Event
) { ... }

The Wrapper: Feature

  • Responsibility: wiring the logic.
  • Dependencies: ViewModel, NavController (via Koin).
  • State: Collects StateFlow from ViewModel.
  • Events: Handles navigation or calls ViewModel functions.
  • Benefit: Isolates dependencies from the rendering logic.
@Composable
fun DefaultCurrencyFeature(
    viewModel: CurrencyViewModel = koinViewModel()
) {
    val state by viewModel.state.collectAsState()
    
    // Wraps the screen
    FeatureScaffold(currentRoute = Routes.CURRENCY) {
        DefaultCurrencyScreen(
            state = state,
            onSelect = viewModel::selectCurrency
        )
    }
}

Rule of Thumb

  • Never pass a ViewModel into a Screen.
  • Never pass a NavController into a Screen.
  • Pass them into the Feature, which orchestrates the Screen.

Clone this wiki locally