-
Notifications
You must be signed in to change notification settings - Fork 0
Screen UI Providers
The ScreenUiProvider pattern is a mechanism used to decouple the "Chrome" of a screen (TopAppBar, FloatingActionButton) from the screen's content and the main navigation host.
In a modular app, the MainScreen (which holds the Scaffold) doesn't know about specific screens inside feature modules. We wanted to avoid a monolithic when(route) statement in MainScreen to determine which Title or FAB to show.
We define an interface ScreenUiProvider in :core:design-system. Each Feature module implements this interface for its screens to expose their UI requirements.
interface ScreenUiProvider {
val route: String
val topBar: (@Composable () -> Unit)? get() = null
val fab: (@Composable () -> Unit)? get() = null
}
In the feature module, we create an implementation binding it to a specific route. We use our custom DynamicTopAppBar here:
class ExpensesScreenUiProviderImpl(
override val route: String = Routes.EXPENSES
) : ScreenUiProvider {
override val topBar: @Composable () -> Unit = {
DynamicTopAppBar(
title = stringResource(R.string.expenses_title),
subtitle = stringResource(R.string.expenses_subtitle),
actions = { /* Filter icons, etc */ }
)
}
// Note: FAB is often null here if handled by the screen itself (see below)
}
We register it in Koin as a ScreenUiProvider:
single { ExpensesScreenUiProviderImpl() } bind ScreenUiProvider::class
In MainScreen, we inject all providers and find the matching one for the current route:
val providers: List<ScreenUiProvider> = getKoin().getAll()
val currentProvider = providers.find { it.route == currentRoute }
Scaffold(
topBar = { currentProvider?.topBar?.invoke() },
floatingActionButton = { currentProvider?.fab?.invoke() }
) { content() }
We have created specialized components to ensure consistency across providers:
A wrapper around Material 3's LargeTopAppBar that automatically handles:
-
Scroll Behavior: Collapses smoothly when the list scrolls (hooked into
MainScreen's scroll state). - Title/Subtitle: Supports a subtitle that fades out as the bar collapses.
-
Back Navigation: Automatically shows the back arrow if an
onBackcallback is provided.
A custom FAB with organic shapes:
- Idle: A 7-point "star" (blob shape).
- Pressed: Morphs into a "flower" shape for tactile feedback.
-
Shared Element: Supports
sharedTransitionKeyto expand into a full screen (e.g., creating a new Expense).
While ScreenUiProvider is excellent for static configuration, it has limitations for animations.
If a FAB needs to animate into a new screen (Container Transform):
- Return
nullforfabin theScreenUiProvider. - Render the
ExpressiveFabinside the Screen composable itself. - This allows the FAB to access the
SharedTransitionScopeof the screen and animate correctly.
Example: The Expenses List screen renders its own "Add Expense" FAB to support the explosion animation when clicked.