What do I attach view models to in a modularized purely compose app?
I have a top level module called :app
. Inside of MainActivity.kt
I have my navigation code that looks like this:
NavHost(navController, startDestination = BottomNavItem.Daily.route) {
composable(BottomNavItem.Daily.route) {
DailyWordScreen()
}
composable(BottomNavItem.Profile.route) {
ProfileScreen()
}
}
The DailyWordScreen()
is a composable in its' own module called :feature:daily
. I want the DailyWordViewModel()
to be encapsulated in the :feature:daily
module. I do not want :app
to have knowledge of the DailyWordViewModel()
. I want my composables to be testable via passing in required args.
Is the solution to have DailyWordScreen()
to become a parent composable that will create the view model inside of this parent composable to pass view model state down into a child composable?
I feel like I need a fragment to host the view model and setup the composable inside setContent {}
.
I'm currently doing this:
@Composable
fun DailyWordScreen(
modifier: Modifier = Modifier,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
dailyWordViewModel: DailyWordViewModel = viewModel()
) {
val state by dailyWordViewModel.state.collectAsStateWithLifecycle()
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
dailyWordViewModel.setupGame()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
This is working fine for me, but is this correct practice?
Based on official Now In Android application, every feature module would have a navigate extension method, for example inside BookmarkNavigation.kt
:
fun NavGraphBuilder.bookmarksScreen(
onTopicClick: (String) -> Unit,
onShowSnackbar: suspend (String, String?) -> Boolean,
) {
composable(route = bookmarksRoute) {
BookmarksRoute(onTopicClick, onShowSnackbar)
}
}
and the BookmarksRoute
, BookmarksViewModel
and BookmarksScreen
would be encapsulated inside :feature:bookmarks
by using internal
, so all your bookmark stuffs would not be exposed to outside, except the navigation method
@Composable
internal fun BookmarksRoute(
viewModel: BookmarksViewModel = hiltViewModel()
) {
val feedState by viewModel.feedUiState.collectAsStateWithLifecycle()
BookmarksScreen(feedState = feedState)
}
your navigation host :app
:
@Composable
fun NavHost(startDestination: String = forYouNavigationRoute) {
val navController = appState.navController
NavHost(
navController = navController,
startDestination = startDestination,
modifier = modifier,
) {
bookmarksScreen() // <=== Here is the extension method
}
}
For your reference: https://github.com/android/nowinandroid/blob/main/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt