Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate setting ScopedServices with deferred initialization #243

Closed
Zhuinden opened this issue Apr 30, 2021 · 13 comments
Closed

Investigate setting ScopedServices with deferred initialization #243

Zhuinden opened this issue Apr 30, 2021 · 13 comments

Comments

@Zhuinden
Copy link
Owner

Zhuinden commented Apr 30, 2021

This is still an issue when using Navigator to create a backstack (backstack is not accessible until Navigator.install() is called). Is there a better workaround rather than creating BackstackHolder?

@matejdro I see a new issue wasn't opened. Does Navigator.configure().setDeferredInitialization(true) + Navigator.executeDeferredInitialization suit your needs? You effectively get a restored Backstack but without setting the StateChanger.

@matejdro
Copy link

matejdro commented May 3, 2021

Sorry, I did not have the chance to check this out until now.

My use case is that I want scoped services to be provided by the dagger (sub)component. But this subcomponent needs backstack on its creation, which creates a bit of circular reference. Currently I'm solving this with the BackstackHolder pattern since none of the dependencies need Backstack immediately, but it is pretty hacky pattern.

This is the furthest I've gotten without BackstackHolder, with deferred initialisation and backstack set after initialisation:

val backstack = Navigator.configure()
    .setStateChanger(AsyncStateChanger(composeStateChanger))
    .setDeferredInitialization(true)
    .install(this, androidContentFrame, History.of(GrocyListKey()))

val activityComponent = component.createActivitySubcomponentFactory().create(backstack)
backstack.setScopedServices(activityComponent.provideScopedServices())

Navigator.executeDeferredInitialization(this)

However, this crashes with the following exception:

     Caused by: java.lang.IllegalStateException: Scope provider should be set before calling `setup()`
        at com.zhuinden.simplestack.Backstack.setScopedServices(Backstack.java:252)

@Zhuinden
Copy link
Owner Author

Zhuinden commented May 3, 2021

Oof, I see what you mean. Theoretically I could either say use assisted injection and rely on serviceBinder.getBackstack() for the backstack reference (which sounds like a nightmare so it's not what you actually want), OR technically I can relax the requirement to "set scoped services before calling setStateChanger" considering before the initial state change, it could be set without issues. 🤔

@matejdro
Copy link

matejdro commented May 3, 2021

Not sure how assisted injection would help here. Then I would have to pass down Backstack manually throughout the whole injection chain which completely defeats the purpose of using DI (might as well continue using BackstackHolder)

What if backstack.setup call would also be deferred if setDeferredInitialization is set? That way backstack is essentially a blank slate when returned from Navigator.install component and can be customized at will until executeDeferredInitialization is triggered.

@Zhuinden
Copy link
Owner Author

Zhuinden commented May 3, 2021

@matejdro released in 2.6.1.

The restriction is now moved so that setScopedServices/setGlobalServices is allowed after setup() and before setStateChanger().

0fcd49a

That way backstack is essentially a blank slate when returned from Navigator.install component

I can't do that because of how state restoration works, but I don't think you'll need that for now.

@Zhuinden Zhuinden closed this as completed May 3, 2021
@Zhuinden Zhuinden added the done label May 3, 2021
@Zhuinden
Copy link
Owner Author

Zhuinden commented May 3, 2021

@matejdro While the issue is closed, please report back if this served your purposes 🚀

@Zhuinden
Copy link
Owner Author

Zhuinden commented May 4, 2021

So basically your snippet with execute deferred init should work now #243 (comment)

@Zhuinden Zhuinden changed the title Investigate creation of backstack before Navigator.install Investigate setting ScopedServices with deferred initialization May 5, 2021
@matejdro
Copy link

matejdro commented May 5, 2021

Wow that was fast. Thanks a lot!

@matejdro
Copy link

matejdro commented May 30, 2021

Actually, this does not seem to fix everything.

After a configuration change, app still crashes on the setScopedServices line:

     Caused by: java.lang.IllegalStateException: Scope provider should be set before the initial state change!
        at com.zhuinden.simplestack.Backstack.setScopedServices(Backstack.java:254)

It seems to me that at this point, backstack is already set from the saved instance state (but scoped services are not, since they are bound to the activity, which is being recreated) and thus I cannot set scoped services.

@Zhuinden Zhuinden reopened this May 31, 2021
@Zhuinden Zhuinden added bug and removed done labels May 31, 2021
@Zhuinden
Copy link
Owner Author

Technically, Backstack exists within the retained scope of the activity, so after rotation, you'd be getting the same Backstack instance which "had already had the state changer set".

I do admit, I was testing this with unit tests, but not in an app.

Theoretically, the following would still give you correct behavior:

if(lastNonConfigurationInstance == null) {
    backstack.setScopedServices(scopedServices)
}

Assuming you aren't trying to pass in an Activity-reference or something. That would always be a memory leak, as the scopes are NOT recreated after config change. 🤔

The sample above is not ideal, but I am also not sure how I would reliably detect that it is allowed now. I guess similarly to when "force execute state change" is called (onDestroy) but it'd be an extra method on Backstack and I'm not sure if I want that. 🤔

Can you check if that works for you?

@matejdro
Copy link

matejdro commented May 31, 2021

Thanks, that worked. Yeah, I see now that setting scoped services at every start is a bit pointless, since they are retained.

Maybe hasScopedServices() check or publicizing didRunInitialStateChange() would make above sample more ideal?

@Zhuinden
Copy link
Owner Author

Zhuinden commented Jun 7, 2021

I will add a new method called canSetScopeProviders(), although API-wise it's a bit shady. Still less shady than having to remember if(lastNonConfigurationInstance == null) { though

@Zhuinden
Copy link
Owner Author

Zhuinden commented Jun 7, 2021

Should be actually done now by 5d8b822 and 2.6.2, meaning that if(lastNonConfigurationInstance == null) { can be replaced with if(backstack.canSetScopeProviders()) {

@Zhuinden Zhuinden closed this as completed Jun 7, 2021
@Zhuinden Zhuinden added the done label Jun 8, 2021
@matejdro
Copy link

matejdro commented Jun 9, 2021

Confirmed working, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants