Galou Minisini
04/01/2022, 12:55 PMHelpView
. The view is a custom view that extends FrameLayout
and LifecycleOwner
.
The view is sometimes being hidden, sometimes being shown. So it goes through the states onAttachedToWindow
and onDetachedFromWindow
where I set my life cycle state:
override fun onAttachedToWindow() {
super.onAttachedToWindow()
lifecycleRegistry.currentState = Lifecycle.State.RESUMED
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
From my init
in the view I am collection the uiState
, a StateFlow emited by my ViewModel.
viewModel.uiState.collectIn(this) { uiState ->
loaderManager.dismiss()
errorNetwork.isVisible = uiState is HelpViewModel.UiState.Error
when (uiState) {
is HelpViewModel.UiState.Loaded -> showContent(<http://uiState.help|uiState.help>)
HelpViewModel.UiState.Loading -> loaderManager.show(R.string.help_loading)
else -> return@collectIn
}
}
The collectIn
function is an extension function:
inline fun <T> Flow<T>.collectIn(
owner: LifecycleOwner,
crossinline onCollect: suspend (T) -> Unit
) = owner.lifecycleScope.launch { owner.repeatOnLifecycle(Lifecycle.State.STARTED) { collect { onCollect(it) } } }
My view is receiving correctly the new states at first but once it's hidden it stops collection the new uiState. My viewModel still emits some new state but the view seems to have stop subscribing to it or is not receiving it for some reason. Any idea why?alex.krupa
04/01/2022, 2:49 PMRuns the given block in a new coroutine when thisSetting state tois at least at state and suspends the execution until thisLifecycle
isLifecycle
.Lifecycle.State.DESTROYED
DESTROYED
in onDetachedFromWindow
may be the culprit.Galou Minisini
04/01/2022, 3:42 PMLifecycle.State.DESTROYED
solve the problem but it also means it never stop observing this sateflow. In that case i might not have a choice but I wonder if there is a better way to handle StateFlow from a custom view? In this case also this view should be a fragment but it's an old project I don't really have the time to change for now but I could solve the problem...Radoslaw Juszczyk
04/01/2022, 5:26 PMalex.krupa
04/02/2022, 3:48 AMrepeatOnLifecycle(STARTED)
?
Here's another part of the method's documentation:
Runs the block of code in a coroutine when the lifecycle is at least.STARTED
The coroutine will be cancelled when theevent happens and willON_STOP
restart executing if the lifecycle receives theevent again.ON_START
alex.krupa
04/02/2022, 4:02 AMRetrieve theresponsible for managing the givenLifecycleOwner
. This may be used to scope work or heavyweight resources associated with the view that may span cycles of the view becoming detached and reattached from a window.View
alex.krupa
04/02/2022, 4:07 AMLifecycle
at all?
Instead have a CoroutineScope
backed by a SupervisorJob
, collect
the flow in onAttachedFromWindow
and cancel the scope in onDetachedFromWindow
.
This is just a rough idea which I haven't tested. However, I remember something similar being done back when RxJava was more popular and I don't see why this wouldn't be doable with coroutines.Radoslaw Juszczyk
04/02/2022, 6:45 AMDESTROYED
in onDetachedFromWindow
may be the culprit.' - I was relating to this, as per the docs the block inside repeatOnLifecycle should be reexecuted when it is back to at least STARTED.
Imo the problem is somewhere else. Maybe lifecycleRegistry is instantiated again at some point and State.RESUMED is passed to the new instance (not the one which was used to start the collection).alex.krupa
04/02/2022, 10:03 AMonDestroy
as a terminal event in Android-world. Once something is destroyed it's not reusable, you can only create a new something.Radoslaw Juszczyk
04/02/2022, 10:52 AMGalou Minisini
04/04/2022, 12:06 PMState.STOPPED
public enum State {
/**
* Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
* any more events. For instance, for an {@link android.app.Activity}, this state is reached
* <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
*/
DESTROYED,
/**
* Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
* the state when it is constructed but has not received
* {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
*/
INITIALIZED,
/**
* Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
* <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
* </ul>
*/
CREATED,
/**
* Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onStart() onStart} call;
* <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
* </ul>
*/
STARTED,
/**
* Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached after {@link android.app.Activity#onResume() onResume} is called.
*/
RESUMED;
/**
* Compares if this State is greater or equal to the given {@code state}.
*
* @param state State to compare with
* @return true if this State is greater or equal to the given {@code state}
*/
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
Radoslaw Juszczyk
04/04/2022, 12:17 PMRadoslaw Juszczyk
04/04/2022, 12:18 PM