androidkotlinandroid-jetpack-composedagger-hilt

Sharing single instance of ViewModel between two composables and recreating it when needed


In my application, there are two nested navigations:

  1. SignedOut
  2. SignedIn

Every time a user navigates from SignedOut to SignedIn, a new instance of SharedViewModel must be provided.

The same instance of SharedViewModel must be shared between composables inside SignedIn.

The whole point of making SharedViewModel is that the app will be able to cache a massive amount of data between multiple composables avoiding fetching the same data several times.

What I tried:

ViewModel:

@HiltViewModel
class SharedViewModel @Inject constructor(): ViewModel() {

    private val instance =
        this.toString().substringAfterLast("@")

    val data = instance
}

Module responsible for providing SharedViewModel:

@Module
@InstallIn(ViewModelComponent::class)
class SharedViewModelModule {

    @Provides
    @ViewModelScoped
    fun provideSharedViewModel(): SharedViewModel =
        SharedViewModel()
}

MainActivity.kt

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {

            val navigationController = rememberNavController()

            NavHost(
                navController = navigationController,
                startDestination = "WelcomeScreen"
            ) {

                navigation(route = "SignedOut", startDestination = "WelcomeScreen") {...}

                navigation(route = "SignedIn", startDestination = "HomeScreen") {

                    composable(
                        route = "HomeScreen",
                        content = {

                            val viewModel = hiltViewModel<SharedViewModel>(it)
                        }
                    )

                    composable(
                        route = "SecondScreen",
                        content = {

                            val viewModel =hiltViewModel<SharedViewModel>(it)
                        }
                    )
                }
            }
        }
    }
}

What I expected:

I navigate from SignedOut to SignedIn: The same instance of SharedViewModel should be provided to both HomeScreen and SecondScreen composables.

I navigate from SignedIn to SignedOut and back to SignedIn: A completely new instance should be provided for HomeScreen and SecondScreen.

Actual result:

Dagger-hilt provides two different instances for HomeScreen and SecondScreen.


Solution

  • Please have a look at the Android ViewModel Cheatsheet:

    ViewModel Cheatsheet

    As you can see denoted in the orange box, a ViewModel in Hilt by default is scoped to the closest ViewModelStoreOwner, which in your case is a BackStackEntry in your NavGraph. However, you can obtain a ViewModel by its BackStackEntry and use it in other destinations.

    Please try the following code:

    composable(
        route = "HomeScreen",
        content = {
            val viewModel = hiltViewModel<SharedViewModel>(it)
        }
    )
    
    composable(
        route = "SecondScreen",
        content = {
            val homeEntry = remember(backStackEntry) {
                navController.getBackStackEntry("HomeScreen")
            }
            val sharedViewModel = hiltViewModel<ParentViewModel>(homeEntry)
            
        }
    )