spotify/mobius

Android Architecture Lifecycle support?

Ghedeon opened this issue · 8 comments

Correct me if I'm wrong, but judging by the wiki, one has to override Activity/Fragment lifecycle methods in order to start/stop/disconnect mobius controller. This boilerplate code could be avoided with a Lifecycle support. So far I ended up with a simple kotlin extension function:

class MobiusLifecycleObserver<MODEL, EVENT>(
	lifecycle: Lifecycle,
	private val controller: MobiusLoop.Controller<MODEL, EVENT>
) : LifecycleObserver {

	init {
		lifecycle.addObserver(this)
	}

	@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
	fun start() = controller.start()

	@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
	fun stop() = controller.stop()

	@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
	fun disconnect() = controller.disconnect()
}

fun <MODEL, EVENT>MobiusLoop.Controller<MODEL, EVENT>.bind(lifecycle: Lifecycle) {
	MobiusLifecycleObserver(lifecycle, this)
}

that could be used like this:

	controller.connect(...)
	controller.bind(lifecycle)

It works like a charm but I'm wondering if a built-in framework support would be beneficial.

@Ghedeon yeah that might work! We're still in the process of defining how to interact with AAC since it can get a little tricky with lifecycle and dependencies. I imagine we'll have an extension at one point with AAC-Mobius utilities. For now we think overriding onResume, onPause and onDestroyView doesn't really enough overhead to warrant lib support yet. But if you believe so, please do create an open source library for it.

Just seems to be a nice thing to have, considering last two Google I/Os and android documentation: https://developer.android.com/topic/libraries/architecture/lifecycle. As you can see, a very similar use-case, where they start with overriding activity lifecycle methods and work their way up towards independent lifecycle-aware component. Among other benefits listed there, easy testing is quite valuable, allowing you to trigger lifecycle events without creating heavy activity/fragment:

val lifecycle = LifecycleRegistry(mockk())
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)

Definitely, not a deal-breaker, more like an extension as you rightly remarked.

I wouldn't recommend using resume/pause for this, that means your app's mobius controller would stop if you click on another app in multi-window mode.

start/stop is better for, well, starting and stopping. Unless you're executing fragment transaction.

@Zhuinden good point! But let's also consider another scenario. If we imagine that our model can be written to a bundle, and basically whenever our fragment is being killed for whatever reason, we save the current model to the bundle, if onSaveInstanceState is called, and later restore it upon recreation and use that as our defaultModel when creating a controller (i.e. we're doing a state restore). Since onSaveInstanceState is called after onPause, if we move the calls to onStop, then it is possible that the model changes after we've already persisted it to the bundle. That means we could be restoring to an older state instead of the latest one.

Since onSaveInstanceState is called after onPause, if we move the calls to onStop, then it is possible that the model changes after we've already persisted it to the bundle.

Yeah that's a pain -_- now I'm starting to understand why they movedonSaveInstanceState after onStop if you target Android P

onSaveInstanceState(): if called, this method will occur after onStop() for applications targeting platforms starting with Build.VERSION_CODES.P. For applications targeting earlier platform versions this method will occur before onStop() and there are no guarantees about whether it will occur before or after onPause()

We're still in the process of defining how to interact with AAC since it can get a little tricky with lifecycle and dependencies.

@anawara do you guys have an update on this? (maybe take a look at MvRx, borrow some ideas 🤷‍♂️) 🍻

Talking about trickiness with AAC, take a look at this Post before using @Ghedeon solution.

When using the Navigation AAC, serving Fragment#getLifecycle will cause multiple LifecycleObserver registrations if you navigate to a new Fragment and then the library Fragment#popBackStack(). I believe this happens when the previous Fragment gets its View destroyed, but the Fragment itself doesn't. The previous LifecycleObserver doesn't get removed and, when bind gets called again, a new LifecycleObserver will be registered.

To solve this, you should use getViewLifecycleOwner().getLifecycle() instead, as the article suggests.

#78 and friends should address most of these concerns, AFAICT. If you are interested in this feature, please take a look and let us know if it helps!