Zhuinden / simple-stack

[ACTIVE] Simple Stack, a backstack library / navigation framework for simpler navigation and state management (for fragments, views, or whatevers).
Apache License 2.0
1.36k stars 76 forks source link

The service does not exist in any accessible scopes #282

Closed MikaReesu closed 2 months ago

MikaReesu commented 9 months ago

Hello,

Recently, in crashlytics, this fatal exception have been triggered several times. I was wondering if you could point me into a direction where I could solve this issue. I don't have this issue on my devices and emulators.

Fatal Exception: java.lang.IllegalStateException The service [com.package.view.MediaViewModel] does not exist in any accessible scopes, the nearest scopes are [com.package.view.OutdatedScreen, SIMPLE_STACK_INTERNAL_GLOBAL_SCOPE]! Is the scope tag registered via a ScopeKey? If yes, make sure the StateChanger has been set by this time, and that you've bound and are trying to lookup the service with the correct service tag. Otherwise, it is likely that the scope you intend to inherit the service from does not exist.

The scenario here seems to be :

The user goes to the app, he his redirected to a screen showing that the app is outdated. He updates the app. He comes back to the app and try to go the Media and the Fatal exception appears here.

Thank you for your help.

Zhuinden commented 9 months ago

Hello,

MediaViewModel would exist if it is bound as a service by a key in the history. When is MediaViewModel accessed in such a way that your view cannot find it, as the OutdatedScreen overwrites this attempt to get it?

If you are using Fragments, I tend to use private val viewModel by lazy { lookup<MediaViewModel>() } and access it in onViewCreated to ensure its initialization.

MikaReesu commented 9 months ago

private val viewModel by lazy { lookup< MediaViewModel >() }

I am also using it in all my fragments.

When it is detected that the app is too old. I set the history to OutdatedScreen and I forward user there.

backstack.setHistory( History.of(OutdatedScreen()), StateChange.FORWARD )

What is weird is that, once app is updated, the app is relaunch I guess, and the splashscreen is triggered which will set the history to the MainScreen. So "normally" OutdatedScreen shouldn't be in the history or at least set as the history.

Zhuinden commented 9 months ago

backstack.setHistory( History.of(OutdatedScreen()), StateChange.FORWARD )

What is weird is that, once app is updated, the app is relaunch I guess, and the splashscreen is triggered which will set the history to the MainScreen. So "normally" OutdatedScreen shouldn't be in the history or at least set as the history.

It sounds like people get redirected from your MediaScreen to OutdatedScreen, but MediaFragment accessed the model when "it is no longer there" as a result of redirection. Without the relevant source code, I could guess something like accessing the model in onDestroy without having been in onCreateView, or as part of an asynchronous operation that finishes only after Fragment is destroyed but async op is not cancelled.

Something like

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val model = model
    ...
}

Can help sometimes. Otherwise, I'd look for what happens when your app restores to MediaFragment after process death.

MikaReesu commented 9 months ago

I don't think that way. The redirection towards the OutdatedScreen is not implemented in that view.

The setHistory(OutdatedScreen) happened in the MainScreen.

So, you think that when there was the redirection towards MediaScreen, the MediaModel was no longer there?

Zhuinden commented 9 months ago

I think what happens is that the MediaFragment is recreated after process death and somehow "later than onViewCreated" attempts to access that VM even though the history is already reset to OutdatedScreen.

MikaReesu commented 9 months ago

Seems strange, any idea how to avoid the crash?

Zhuinden commented 9 months ago

It depends on your code, which I don't see. There should theoretically be no such timing based problems since... a long time ago (2.1.0). The bug itself is not in simple-stack, so there is nothing I can fix. If you don't really know why MediaFragment exists despite the keys being reset, make sure your key classes are data class or override getFragmentTag() (they are getClassName() by default, but I usually use data class toString to support different tags based on parameters).

The hackiest "fix attempt bandaid" can be putting your setHistory call in handler.post {} but it should not be necessary.

MikaReesu commented 9 months ago

I see, thank you.

Zhuinden commented 9 months ago

Notify back if you figured out why your scope is missing.

Zhuinden commented 6 months ago

Hello, have you figured it out? I totally forgot this was open. @MikaReesu

Zhuinden commented 2 months ago

I will assume this was fixed.