freeletics/khonshu

navigation-experimental: LocalViewModelStoreOwner, LocalLifecycleOwner, LocalSavedStateRegistryOwner

Opened this issue ยท 3 comments

Hi, first of all, thanks for your awesome library ๐Ÿ’ฏ

In androidx compose navigation, I see saveableStateHolder.SaveableStateProvider(content) is in CompositionLocalProvider that provides ViewModelStoreOwner, LifecycleOwner and SavedStateRegistryOwner.

https://github.com/androidx/androidx/blob/4b85e9bb64eb4026256d6a39837fe89722fac220/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt#L43

What about adding that to navigation-experimental @Composable fun Show(...)?
This issue is just my thought/question about it ๐Ÿ˜„

Other than SaveableStateProvider itself which we already support, I'd like to generally avoid the other 3 since it introduces a lot of complexity to support them.

If you also use the codegen we have replacements for ViewModelStoreOwner and SavedStateRegistryOwner. For the former you can simply annotate any object that is provided by Dagger with @ScopeTo and it will survive configuration changes, which is the primary use case for view models. You can also simply inject a SavedStateHandle into classes.

As long as you're fine with opting into InternalNavigationApi the underlying mechanisms for what the codegen uses are also usable without it. Within the NavHost you can obtain a NavigationExecutor through LocalNavigationExecutor. The executor has a savedStateHandleFor method to obtain a SavedStateHandle for a specific destination and it has storeFor which returns a NavigationExecutor.Store. That store holds on to objects, survives configuration changes and is automatically cleared when the destination is removed from the back stack.

There is nothing for LifecycleOwner but we didn't really have the need to destination/backstackentry level lifecycles so far.

Hi @gabrielittner, thanks for your reply.

Currently, I'm using Freeletics Navigation without Codegen.

  • When using navigation-compose artifact, because it wraps AndroidX Compose Navigation, ViewModel and collectAsStateWithLifecycle() tie to the NavBackStack's lifecycle will work as expected.

  • If I use navigation-experimental, I can use executor.storeFor(destinationId) as you mentioned,

    public inline fun <reified C : Any, PC : Any, R : BaseRoute> component(
    destinationId: DestinationId<out R>,
    route: R,
    executor: NavigationExecutor,
    context: Context,
    parentScope: KClass<*>,
    crossinline factory: (PC, SavedStateHandle, R) -> C,
    ): C {
    return executor.storeFor(destinationId).getOrCreate(C::class) {
    val parentComponent = context.findComponentByScope<PC>(parentScope)
    val savedStateHandle = executor.savedStateHandleFor(destinationId)
    factory(parentComponent, savedStateHandle, route)
    }
    }
    , to replace ViewModel and SavedStateHandle as well.

  • About collectAsStateWithLifecycle() tie to the NavBackStack's lifecycle.
    Usually, it is used with stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), ...) to avoid Keeping resources active in the background (https://developer.android.com/topic/architecture/recommendations and https://manuelvivo.dev/consuming-flows-compose).
    How can I achieve this without NavBackStack's LifecycleOwner?

Thank you so much!

For the use case of avoiding resources being active in the background the default lifecycle owner from the Activity should be enough. There are 2 cases unless I'm missing one

  • the screen is on the back stack but currently not visible -> in this case the composable is not shown so regardless of the lifecycle collectAsStateWithLifecycle wouldn't run since it's not part of the current composition
  • the whole app/activity is in background -> the Activity lifecycle will be stopped

So even without a back stack entry specific lifecycle background work should get stopped.