compose-navigation-reimagined icon indicating copy to clipboard operation
compose-navigation-reimagined copied to clipboard

Obtaining ViewModel for previous destinations on back stack

Open equeim opened this issue 2 years ago • 6 comments

With androidx.navigation you have getBackStackEntry method that returns implementation of ViewModelStoreOwner interface for any destination that is currently on back stack. This is handy for sharing state between parent and child screens without having to scope ViewModel to activity (which makes your view model live too long if you want to share view model from destination that is not root).

AFAIK there is no API for this in this library - you can only get ViewModelStoreOwner for current destination or whole Activity.

equeim avatar Jul 18 '22 22:07 equeim

Yeah, I was thinking about this as well. But I always thought it is a bit weird to access neighbours' ViewModels, but if anyone uses it that way then yeah, sure, I'll add it.

I'll try to get to it sooner, it shouldn't be a big change to add API for that.

olshevski avatar Jul 19 '22 09:07 olshevski

Considering that Google recommends this pattern with androidx.navigation, probably quite a few people use it :) (the wording there is a bit weird since it works not only with whole graphs but with single destinations too, i.e. sub-graphs starting with given destination).

equeim avatar Jul 19 '22 10:07 equeim

Yeah, I believe they originally intended for it to be used just with navigation graphs and nested sub-graphs, but no one stops you from using any destination in the backstack to share a ViewModel.

At least with navigation graphs and their destinations there is this parent-child relationship. ViewModels of a parent would always have a larger scope than their children's ViewModels, so the shared parent's ViewModel automatically outlives the child's one, which is convenient for sharing.

The problem with accessing neighbours' ViewModels is that (at least in my library) destinations may be reordered (e.g. with moveToTop method). This may lead to rare error-prone cases when the ViewModel shared this way may be cleared too early, so a developer need to make sure this reordering never happens to such a destination.

But yeah, there isn't much options in the library to choose from at the moment. The only way to share a ViewModel between several destination is to create a nested NavHost. Sometimes it is convenient to do so, but other time it makes the navigation unnecessarily multi-layered and overcomplicated.

Anyways, I see no problems with adding the API. But I have ideas for the future on how to add functionality to share ViewModel between arbitrary destination in a more well-defined way.

olshevski avatar Jul 19 '22 10:07 olshevski

@equeim I've created a snapshot version with the requested API implementation. There is no documentation yet, but otherwise it should be usable. Can you please check it out and give feedback?

To use the API, please add snapshots repository:

repositories {
    ...
    maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

and use this snapshot version:

implementation("dev.olshevski.navigation:reimagined:1.1.2-20220720.125926-2")

There is now a NavHostScope that is available inside NavHost/AnimatedNavHost and it provides findNavHostEntry method. The method returns non-null NavHostEntry if it is present on the backstack. NavHostEntry is what actually implements ViewModelStore.

olshevski avatar Jul 20 '22 13:07 olshevski

Thanks, it seems to be working (including when app is restored from process death). Though it's a bit strange from library user point of view why there is NavHostScope interface if we already have NavController. There is also duplication of functionality between them - both have backstack property.

equeim avatar Jul 27 '22 19:07 equeim

Though it's a bit strange from library user point of view why there is NavHostScope interface if we already have NavController.

NavHostScope is inevitable because NavController is by design should be a simple data structure that is oblivious to anything related to lifecycle components and activity itself. This was a major point while designing this library's architecture and not following the original Navigation Component completely.

NavHostEntries that implement ViewModelStoreOwner interface exist only within NavHost, so it actually makes some sense to define a scope within it.

There is also duplication of functionality between them - both have backstack property.

Yeah, it is a bit flimsy. I'll work on this. There are some issues with this API, so I wouldn't rush the release.

olshevski avatar Jul 28 '22 08:07 olshevski

Final API is available in 1.2.0

NavHostScope is here to stay. It now exposes all the current NavHostEntries in the same order their associated entries appear in the backstack. findNavHostEntry is also renamed to findHostEntry.

olshevski avatar Aug 22 '22 15:08 olshevski