adrielcafe / voyager

🛸 A pragmatic navigation library for Jetpack Compose
https://voyager.adriel.cafe
MIT License
2.6k stars 139 forks source link

ScreenModel created by Koin inside a Tab is recreated on Back Press #266

Open Vaibhav2002 opened 11 months ago

Vaibhav2002 commented 11 months ago

I have 1 screen ScreenA and another screen ScreenB which has a bottom nav with tabs using TabNavigator

Screen Code:

@Composable
override fun Content() {
    val viewModel = getScreenModel<NewsViewModel>()
    Napier.d { viewModel.toString() }
}

Tab Code:

object FeedTab : Tab {
    override val key = "Feed" //Even tried with unique key
    override val options: TabOptions
        @Composable get() {
            val icon = rememberVectorPainter(Icons.Outlined.Home)
            return remember {
                TabOptions(
                    index = 0u,
                    title = "Feed",
                    icon = icon
                )
            }
        }

    @Composable
    override fun Content() {
        val viewModel = getScreenModel<FeedViewModel>()
        Napier.d { "FeedBottomTab ViewModel = " + viewModel.toString() } 
    }
}

Navigator Setup:

 Navigator 
├── Screen A
├── Screen B (has a Tab Navigator)
│     ├── Tab A
│     └── Tab B
└── Screen C

Now when i navigate from Screen A to Screen B and come back I get the same ViewModel instance of Screen A being logged

If I am on ScreenB, with 1st tab open and navigate to a different screen say Screen C Now when I come back I have a different instance of ViewModel being logged

frontiertsymbal commented 11 months ago

Use

val screenModel = rememberScreenModel { getKoin().get<some class>() }

Vaibhav2002 commented 11 months ago

Use

val screenModel = rememberScreenModel { getKoin().get<some class>() }

The getScreenModel<>() internally does this

Vaibhav2002 commented 11 months ago

@DevSrSouza Any idea on this?

Shinokuni commented 8 months ago

I ran on the exact same problem: when opening a screen from a tab and going back, the current tab viewModel/screenModel is recreated.

Tommyten commented 8 months ago

This behaviour doesn't seem to be dependent on Koin. I'm testing out Voyager currently and have a similar setup:

The root navigator contains a Screen A with a TabNavigator. Tab A is the default Tab, and has a ScreenModel to it. When I push a new Screen B onto the root navigator, and then go on to pop it from the stack, the ScreenModel of Tab A is recreated.

babramovitch commented 8 months ago

I'm having this problem as well and it's very disruptive to the user experience.

babramovitch commented 8 months ago

Okay I followed the code path of and found the ScreenModels were being disposed of when a flag called disposeNestedNavigators was true, and by default its true.

You can change this when you instantiate your navigator

    Navigator(
        screen = HomeScreen()
        disposeBehavior = NavigatorDisposeBehavior(
            disposeNestedNavigators = false,
            disposeSteps = true  <--- I haven't really looked into what this one does, but its defauled true
        )
    )

Also, this seems like behaviour that we should be able to set during each push, not a setting that gets set once.

navigator.push(NewScreen(), disposeBehaviour = ...)

Tommyten commented 8 months ago

@babramovitch This solves the problem for me. I guess this feature should be documented more. I think this should also close this issue.

shpasha commented 7 months ago

I suppose that in this case if you navigate from Screen C immediately back to Screen A your ViewModels inside Tab A and Tab B will not be cleared

I reported this issue here https://github.com/adrielcafe/voyager/issues/396

hristogochev commented 6 months ago

This issue is not present if you create a navigator for the specific tab you are on and then push a screen to the root navigator:

 Navigator 
├── Screen A
├── Screen B (has a Tab Navigator)
│     ├── Tab A
│     │     ├── Nested Tab A Navigator
│     └── Tab B
└── Screen C
val nestedTabANavigator = LocalNavigator.currentOrThrow
val tabNavigator = nestedTabANavigator.parent ?: error("CompositionLocal is null")
val rootNavigator = tabNavigator.parent ?: error("CompositionLocal is null")

rootNavigator.push(NewScreenA())

UPDATE: The issue is not present in this case because TabNavigators, by default, don't dispose of their nested navigators. https://github.com/adrielcafe/voyager/issues/402

It appears that TabNavigators are disposed of unintentionally when navigating to another screen that overlays the one containing them.