Hi i am struggling to understand the lifecycle of...
# compose
t
Hi i am struggling to understand the lifecycle of my viewmodels within my current android jetpack compose application
I have multiple composable screens all displayed from a single activity. each screen has its own viewmodel and associated repository. I have added logging to all my viewmodel
onCleared()
functions. I was expecting to see onCleared() on each viewmodel as i navigate between screens, e.g. navigating from Screen A to B i thought i would see viewmodel A
onCleared()
triggered, then when i navigate from Screen B to C i thought i would see viewmodel B
onCleared()
triggered this is not happening though. can someone explain why my expectations are wrong?
z
ViewModels would be entirely useless if onCleared would be called the moment you go from A>B, after all you want to retain information when going back from B>A.
Activities work the same way. OnCleared is only called after onDestroy when the activity is finishing, not after rotation or navigation
t
a good point well made 😄 however what i am seeing is that i). when i navigate Quickly from Screen
A -> B -> C -> A
I am logging onCleared() calls for both A and B ii). when i navigate S l o w I y from Screen
A -> B -> C -> A
I am not logging any onCleared()
z
Them I’m guessing your ViewModel doesn’t have the correct lifecycle owner. Is it tied to the navBackStackEntry?
t
how would i tell that? i am using Hilt and have my repositories viewmodelscoped
z
How are you creating your ViewModels? Can you share some code
t
Copy code
@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = hiltViewModel()
) {
    val context = LocalContext.current
like that 😄
Copy code
@HiltViewModel
class MyViewModel @Inject constructor(private val repository: MyRepository) : BaseViewModel() {
...also
Copy code
@InstallIn(ViewModelComponent::class)
@Module
object RepositoryModule {

    @Provides
    @ViewModelScoped
    fun provideMyRepository(repository: MasterRepository): MyRepository {
        return MyRepository(repository)
    }
}
z
I’m on mobile so I’m a bit slow but inside hiltViewModel I’m passing the back stack entry
Maybe that helps with correctly retaining the VM
t
doesnt that code alreday do that for free?
Copy code
/**
 * Returns an existing
 * [HiltViewModel](<https://dagger.dev/api/latest/dagger/hilt/android/lifecycle/HiltViewModel>)
 * -annotated [ViewModel] or creates a new one scoped to the current navigation graph present on
 * the {@link NavController} back stack.
 *
 * If no navigation graph is currently present then the current scope will be used, usually, a
 * fragment or an activity.
 *
 * @sample androidx.hilt.navigation.compose.samples.NavComposable
 * @sample androidx.hilt.navigation.compose.samples.NestedNavComposable
 */
@Composable
inline fun <reified VM : ViewModel> hiltViewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    }
): VM {
    val factory = createHiltViewModelFactory(viewModelStoreOwner)
    return viewModel(viewModelStoreOwner, factory = factory)
}

@Composable
@PublishedApi
internal fun createHiltViewModelFactory(
    viewModelStoreOwner: ViewModelStoreOwner
): ViewModelProvider.Factory? = if (viewModelStoreOwner is NavBackStackEntry) {
    HiltViewModelFactory(
        context = LocalContext.current,
        navBackStackEntry = viewModelStoreOwner
    )
} else {
    // Use the default factory provided by the ViewModelStoreOwner
    // and assume it is an @AndroidEntryPoint annotated fragment or activity
    null
}
ok let me ask another related question my "actual" issue is that my viewmodels have a stateflow that i need to reset whenever the user navigates to a different screen how can i "elegantly" have my stateflow initialised whenever the user navigates to a screen? or away from a screen? currently the viewmodel is keeping the last value where i need a fire and forget flow
😄
In other news... this stackoverflow question seems to cover what i am after i need screen state scope and scoped state to be seperate https://stackoverflow.com/questions/64955859/scoping-states-in-jetpack-compose
i
If you want something tied to the lifetime of composition and not to the screen being on the back stack, then it should be part of composition, not part of a ViewModel at all. Just create a regular object and
remember
it
t
that would seem like a possible solution, however i am attempting to use MVI and have a single source of ui state (a combined state flow) and placing one of my data sources in the composable would break my MVI design. I think the issue is caused by my compose navigation being configured to save/restore state, this allows me to have multiple backstacks and among other things lets each screen keep its list scroll position when the user navigates between screens, i cannot see an approach where i can make one of my viewmodel data sources "Transient" e.g. not be saved and restored as the user navigates to and from a composable screen 🤔
201 Views