Martin
Martin

Reputation: 2914

Global ViewModel in Jetpack Compose

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

Answers (2)

Thracian
Thracian

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 ViewModels 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

Jan Itor
Jan Itor

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

Related Questions