androidandroid-jetpack-composeandroid-viewmodeldagger-hilt

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.


Solution

  • 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.