voyager icon indicating copy to clipboard operation
voyager copied to clipboard

Nested navigators inside Tabs don't get disposed when the TabNavigator gets disposed.

Open hristogochev opened this issue 1 year ago • 3 comments

When disposing of a TabNavigator, the nested navigators inside each Tab don't get disposed. This means that the ScreenModels inside each navigator don't get disposed as well.

I've solved the problem by taking the source code of the voyager-tab-navigator dependency and making some minimal changes.

The solution is published in this gist: Fix for Voyager TabNavigator not disposing of nested navigators.

Essentially, the solution involves mapping the nested navigator created inside each Tab to the Tab itself so that it can be disposed of when the TabNavigator is disposed.

If you need more than one nested navigator inside a tab, feel free to make the navigators a list instead, but I think this is enough.

This also helps partly resolve https://github.com/adrielcafe/voyager/issues/396.

hristogochev avatar Apr 29 '24 01:04 hristogochev

Thanks @hristogochev for the idea. In my case, I just hat to implement a different TabDisposable:


@OptIn(InternalVoyagerApi::class)
@Composable
fun TabDisposable(tabs: List<Tab>) {
    val navigator = LocalNavigator.currentOrThrow
    DisposableEffectIgnoringConfiguration(Unit) {
        onDispose {
            tabs.forEach {
                navigator.parent?.dispose(it)
            }
        }
    }
}

osrl avatar Oct 30 '24 21:10 osrl

Nice! This solution would work if you don't have a nested navigator inside each tab. If you only have a single screen inside each tab it's good enough.

hristogochev avatar Oct 30 '24 22:10 hristogochev

I am using a TabScreen, which I can push other screens over it and wanted my tabs to not be disposed. Unfortunately the side effect of this is that when the TabScreen is replaced or popped the tabs will not get disposed. Not exactly the same, but solution could be applied the same. My solution is to use the new LifecycleEffectOnce to check when we are disposed and dispose the nested navigators ourselves. This example is for tabs, but you can use navigator.items to loop through them instead of the tabs:

                // Get navigator from LocalNavigator, passed navigator has internal real navigator...
                val navigator = LocalNavigator.current
                // This will only be called once the screen gets disposed (so if we are pushing, it will not get disposed until we are replaced or popped)
                LifecycleEffectOnce {
                    onDispose {
                        navigator?.items
                        // If we are disposed, also dispose nested tabs
                        BottomTabs.forEach {
                            navigator?.dispose(it)
                        }
                    }
                }

This code should be inside the navigator content. Otherwise LocalNavigator.current will get the parent navigator.

kevinvanmierlo avatar Dec 20 '24 09:12 kevinvanmierlo