Someone
Someone

Reputation: 21

View Model with Jetpack compose view

I am using ViewModelFactory to obtain view model instance which is to be used by my jetpack compose view.

class AchievementsScreenViewModelFactory() :
    ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = AchievementsScreenViewModel() as T
}

As soon as instantiate my viewmodel, i want to perform some operations. I am currently storing those operations in the viewmodel constructor(Like some firebase operation to check if the user instance is found or not).Is that a wrong practice? if so, what should i do?

    constructor(context:Context) : this() {
        this.context=context
        mAuth= FirebaseAuth.getInstance()
        if(mAuth.currentUser!=null){
            triggerNavigateEvent(Screen.DashboardScreen)
        }
    }

So, the issue that I am facing is that, whenever I use my View Model Factory to instantiate an instance of my view and then when i pop the view from the NavController and return to it, the View Model Factory returns me the same instance of the View Model and the tasks that are present in my constructor are not being performed.
Is there a way to kill the instance of my View Model at the time of popping the screen from the NavController? or is there an other way?

I am calling the viewmodel from the composable screen like this

@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun LoginScreen(navController: NavHostController
){

    var viewModel:LoginScreenViewModel= viewModel(
        factory = LoginScreenViewModelFactory(LocalContext.current)
    )
    .
    .
    .
}

I am navigating to the screens using google accompanist navigation library.

AnimatedNavHost(
        navController = navController,
        startDestination = Screen.SplashScreen.route,
        enterTransition = { fadeIn(animationSpec = tween(1000), initialAlpha = 0f) },
        exitTransition ={ fadeOut(animationSpec = tween(1000), targetAlpha = 0f) }
    ){
        composable(
            route = Screen.LoginScreen.route
        ){
            LoginScreen(navController = navController)
        }
}

Upvotes: 1

Views: 2622

Answers (2)

Devrath
Devrath

Reputation: 42824

Here is an example I use

 val viewModel = hiltViewModel<PokemonListVm>()

Usage:

@Composable
fun PokemonListScreen(
    navController: NavController
) {

    val viewModel = hiltViewModel<PokemonListVm>()
    val lazyPokemonItems: LazyPagingItems<PokedexListEntry> = viewModel.pokemonList.collectAsLazyPagingItems()

    Surface(
        color = MaterialTheme.colors.background,
        modifier = Modifier.fillMaxSize()
    ) {

        Column {
            Spacer(modifier = Modifier.height(20.dp))
            PokemonBanner()
            PokemonSearch()
            PokemonLazyList(
                pokemonList = lazyPokemonItems,
                onItemClick = { entry ->
                    navController.navigate(
                        "pokemon_detail_screen/${entry.dominentColor.toArgb()}/${entry.pokemonName}"
                    )
                }
            )
        }
    }
}

Upvotes: 0

Ma3x
Ma3x

Reputation: 6549

The navigation-compose NavHost (in your case AnimatedNavHost) will call its composablefunction for the target destination every time the destination changes, i.e. when you navigate to a destination and also when you navigate back. That means that you can put the code that you want to run into a method/function in your ViewModel (instead of its constructor) and use a LaunchedEffect composable to call it. If you use a constant key when invoking the LaunchedEffect composable, for example LaunchedEffect(Unit), it will only run once when it enters the composition, in your case once each time the destination changes.

Move the code from VM constructor to a new function in your VM

    suspend fun callSomeApi() {
        // your code here
    }

And add a LaunchedEffect(Unit) to the composable you want to call this new function from

@Composable
fun LoginScreen(navController: NavHostController){
    var viewModel: LoginScreenViewModel = viewModel(
        factory = LoginScreenViewModelFactory(LocalContext.current)
    )

    // called once every time this composable enters the composition
    LaunchedEffect(Unit) {    
        viewModel.callSomeApi()
    }
}

Upvotes: 1

Related Questions