Colton Idle
08/11/2021, 12:59 AM@Composable
fun DebugPanelBottomSheet(viewModel: NewCreditCardViewModel = hiltViewModel()) {
val myClickLambda = {
viewModel.state.cardNumber = "1234 1234 1234 1234"
}
//button that passes myClickLamda
Colton Idle
08/11/2021, 1:39 AM@Composable
fun MyExample(
// Returns the same instance as long as the activity is alive,
// just as if you grabbed the instance from an Activity or Fragment
viewModel: ExampleViewModel = viewModel()
) { /* ... */ }
@Composable
fun MyExample2(
viewModel: ExampleViewModel = viewModel() // Same instance as in MyExample
) { /* ... */ }
Ian Lake
08/11/2021, 1:49 AMviewModel()
and hiltViewModel()
use the LocalViewModelStoreOwner
by default. Which, if you're using Navigation, is the containing NavBackStackEntry
Berkeli Alashov
08/11/2021, 1:49 AMIan Lake
08/11/2021, 1:49 AMColton Idle
08/11/2021, 2:08 AMReturns an existing HiltViewModel -annotated ViewModel or creates a new one scoped to the current navigation graph present on the {@link NavController} back stack.Both my NewCreditCardScreen and my DebugPanelBottomSheet are on the screen at the same time? Actually, maybe it's how my DebugPanelBottomSheet is setup...
Colton Idle
08/11/2021, 2:10 AMsetContent {
MyAppTheme() {
DebugPanelBottomSheet() {
AppRouter()
}
}
}
and AppRouter contains the actual NavHost.
NavHost(navController, startDestination = Screen.Main.route, Modifier.padding(innerPadding)) {
Colton Idle
08/11/2021, 2:12 AMsetContent {
MyAppTheme() {
DebugPanelBottomSheet() {
AppRouter()
}
}
}
Colton Idle
08/11/2021, 2:16 AMColton Idle
08/11/2021, 2:24 AMModalBottomSheetLayout(sheetState = state, sheetContent = { DebugBottomSheet() }) {
Box() { AppRouter() }
}
Colton Idle
08/11/2021, 2:43 AMvar currentScreenViewModel : ViewModel? = null
to my activity. And then in the NewCreditCardScreen I just do
val context = LocalContext.current
(context as MainActivity).currentScreenViewModel = viewModel
and then everything works in my bottom sheet since I actually have the ViewModel. Definitely prone to a memory leak here... but I'll go this route for now in lieu of better ideas.Berkeli Alashov
08/11/2021, 2:46 AMhiltViewModel
before NavHost, won't they have the same activity scope?Colton Idle
08/11/2021, 2:58 AMIan Lake
08/11/2021, 3:01 AMhiltViewModel(LocalContext.current as ViewModelStoreOwner)
Ian Lake
08/11/2021, 3:01 AMIan Lake
08/11/2021, 3:04 AMhiltViewModel(navController.getBackStackEntry("your_root_route"))
(using the route you define at the NavHost
level)Colton Idle
08/11/2021, 3:06 AMColton Idle
08/11/2021, 3:20 AMhiltViewModel(LocalContext.current as ViewModelStoreOwner)
on the DebugBottomSheet then I still get different instances.
If I add it on every instance of hiltViewModel hiltViewModel(LocalContext.current as ViewModelStoreOwner)
then I get the same instance! This seems like what I want, but in this case I'm losing my nav scoped VMs right? I just want to make sure I understand the "risks" before accepting them. Since this concept of "screen shortcuts" is everywhere, this creditCard screen is just the first example, but we'll likely have "screen shortcuts" on just about every screen so I'm trying to find the approach that is most maintainable.
But yeah, I guess what makes this tricky is that DebugBottomSheet is the same composable, but depending on what screen is currently showing, I want it to change it's "screen shortcuts" list of buttons contextually, and with that, it means that I should be able to grab any ViewModel at will and modify it. Sort of like "god mode" in a video game.
If any of the above info changes your opinion on my approach, then please let me know! Thank youIan Lake
08/11/2021, 5:15 AMViewModelStoreOwner
- that's the default provided by LocalViewModelStoreOwner
Ian Lake
08/11/2021, 5:17 AMLocalViewModelStoreOwner
. It is only when you are inside a composable
destination in your graph do you get a LocalViewModelStoreOwner
set on only that composition tree that scopes your ViewModel to that screenIan Lake
08/11/2021, 5:18 AMModalBottomSheetLayout
with a sheetContent
by necessity means that it has no access to any LocalViewModelStoreOwner
that is set on a completely different part of the compose hierarchyIan Lake
08/11/2021, 5:19 AMsheetContent
an entry in the NavHostIan Lake
08/11/2021, 5:21 AMnavController.getBackStackEntry("your_route")
or, what I think would be even better for your use case of launching the bottom sheet from any destination is navController.previousBackStackEntry
- i.e., you'll know exactly what destination launched your bottom sheet (navController.previousBackStackEntry!!.destination.route
) and be able to pass that through to hiltViewModel
to gain access to ViewModels in that previous entry's scopeColton Idle
08/11/2021, 2:18 PMThat means it is already using the activity'sThat makes sense.- that's the default provided byViewModelStoreOwner
LocalViewModelStoreOwner
It is only when you are inside aAlso makes sense.destination in your graph do you get acomposable
set on only that composition tree that scopes your ViewModel to that screenLocalViewModelStoreOwner
Which is why Accompanist Navigation Material is so useful in cases like this - it makes thatLost me slightly, there. I thought you would say "it makes bottomsheet an entry in the navhost" Any reason you specifically called out sheetContent?an entry in the NavHostsheetContent
(Oh yeah. That sounds like what I want. So I would only have to pass that through the bottom sheet contents hiltViewModel right? There's no reason to do that for each actual screen in my app?) and be able to pass that through tonavController.previousBackStackEntry!!.destination.route
to gain access to ViewModels in that previous entry's scopehiltViewModel
Colton Idle
08/12/2021, 12:55 AMnavController.previousBackStackEntry!!.destination.route
into hiltViewModel?Colton Idle
08/12/2021, 1:05 AMval viewModel =
hiltViewModel<ViewModel>(
navController.getBackStackEntry(
navController.previousBackStackEntry!!.destination.route!!))
but it crashes because it seems like I can't declare ViewModel as a type... it wants me to declare the specific VM type. i.e. CreditCardScreenViewModel.
Hm...Ian Lake
08/12/2021, 3:41 AMpreviousBackStackEntry
is a ViewModelStoreOwner
, so pass it directly inIan Lake
08/12/2021, 3:42 AMhiltViewModel<YourViewModel>(navController.previousBackStackEntry!!)
Ian Lake
08/12/2021, 3:43 AMColton Idle
08/12/2021, 5:58 AMval viewModel =
hiltViewModel<ViewModel>(navController.previousBackStackEntry!!)
when (viewModel)
is CreditCardScreenViewModel { ButtonToAutoFillCreditCardDetails() }
is SignUpScreenViewModel { ButtonToAutoFillNewUserSignUp() }
But you are right. hiltViewModel<YourViewModel>(navController.previousBackStackEntry!!)
worked!
I will just create a when statement on the previousBackStackEntry.route, then create a concrete VM and I'm in the clear. Phew!
Thank you @Ian Lake . Couldn't do it without you (or @jossiwolf either) 😅