Reputation: 2914
I have an question. How do I use global ViewModel instance which can be injected by Dagger/Hilt into any composables as shared instance?
I have MainNavigationViewModel which is handling some measurements tied to navigation bars and I want to share those measurements in some screens within NavHost.
So I'm initializing this ViewModel in my Activity like this:
@AndroidEntryPoint
class MainActivity : BaseActivity() {
override val viewModel : MainNavigationViewModel by viewModels()
override val screen: @Composable () -> Unit = {
MainNavigation(viewModel = viewModel)
}
}
And in individual composables I just use
@Composable
fun ScreenA(
navigationViewModel: MainNavigationViewModel = hiltViewModel(),
)
and then collecting MutableState
using viewmodel.state.collectAsStateWithLifecycle()
But for some reason it is returning wrong values (default ones from mutable state). Seems like every time I inject this ViewModel
it is different instance of it. I need shared instance.
So if MainNavigation will update some MutableState value inside this ViewModel, all screens where this ViewModel is defined will recompose if needed.
Upvotes: 0
Views: 193
Reputation: 67248
What i get from the question is you want to share ViewModel between BaseActivities
, if that's the case check answer below if it's passing between NavBackStackEntries use Jan Itor's answer. Because NavStackEntry is also ViewModelStoreOwner as ComponentActivity
, using Activity let's you create a VieWModel tied to ComponentActivity
.
You can't have global ViewModel
s in android by default because ViewModel is created via ViewModelStore from ViewModelOwner
implementation which is the biggest scope is ComponentActivity for that. Which means you can have ViewModel's tied to current Activity or BaseActivity
in your case and you can't share ViewModels between activities like you can do with fragments.
https://developer.android.com/topic/libraries/architecture/viewmodel#lifecycle
The lifecycle of a ViewModel is tied directly to its scope. A ViewModel remains in memory until the ViewModelStoreOwner to which it is scoped disappears. This may occur in the following contexts:
In the case of an activity, when it finishes. In the case of a fragment, when it detaches. In the case of a Navigation entry, when it's removed from the back stack.
But you can create @Singleton
SomeXManager or repository classes that you can pass between two ViewModels belong to different Activities.
Also, maybe if you implement ViewModelStoreOwner in your application class you might be able to create ViewModels with application scope.
Upvotes: 0
Reputation: 4276
To scope the injected ViewModel
to the activity, provide the activity as viewModelStoreOwner
, for example:
fun Context.findActivity(): ComponentActivity? = when (this) {
is ComponentActivity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
@Composable
fun ScreenA(
navigationViewModel: MainNavigationViewModel = hiltViewModel(LocalContext.current.findActivity()!!),
)
//Or
@Composable
fun ScreenA(
navigationViewModel: MainNavigationViewModel = hiltViewModel(
checkNotNull(LocalContext.current.findActivity()) { "No ViewModelStoreOwner found" }
),
)
There is no need to create that ViewModel
in MainActivity
.
Upvotes: 0