Upgrade to Pro — share decks privately, control downloads, hide ads and more …

LifecycleOwners and Flows: Cleaning the UI

LifecycleOwners and Flows: Cleaning the UI

We are going to talk about StateFlow, SharedFlow and Lifecycle owners and how we can collected the first two in a safety way inside the UI, taking in special consideration the memory consumed by the collection, also we will address topics about Owners from Activities, fragments and your general application including things like Network!

C6e1201f51c1ff186edba69f98476f15?s=128

Dinorah Tovar

May 04, 2021
Tweet

Transcript

  1. LifecycleOwners and Flows: Cleaning the UI Dinorah Tova r Google

    Developer Expert Platform Mobile Engineer
 @ konfío.mx @ddinorahtovar @ddinorahtovar
  2. Let’s discuss LifecycleOwners

  3. Everything has lifecycle @ddinorahtovar 
 General Application Activity Fragment •Defines

    an object that has an Android Lifecycle Classes
  4. Lifecycle depends of more things •OnLifecycle Event •ViewTree 
 Lifecycle

    
 Owner •Process 
 Lifecycle 
 Owner LifecycleOwner Lifecycle •State •Events •Especially Lifecycle and LifecycleOwner @ddinorahtovar
  5. Lifecycle States @ddinorahtovar •States are stages in a graph Name

    Operation States DESTROYED, INITIALIZED, CREATED, STARTED, RESUMED For Destroy, creates and initialize all the states of a LifecycleOwner
  6. Lifecycle Events @ddinorahtovar •Events are the edges between these stages.

    ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, ON_ANY Can be used by custom components to handle lifecycle changes Name Operation Event For
  7. Lifecycle Events •Events - can be used on regular classes

    •To track OnResume and OnPause •To track NetworkState @ddinorahtovar
  8. ProcessLifecycleOwner •Provides lifecycle for the whole application process 
 General

    Application Activity Fragment Dialog @ddinorahtovar
  9. Lifecycle Events class AppOwner() : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStartApplication()

    { // App was resumed or started } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStopApplication() { // App has been moved to background // includes switch to other app // includes just moving to task manager } } @ddinorahtovar
  10. Lifecycle Events class AppOwner() : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStartApplication()

    { // App was resumed or started } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStopApplication() { // App has been moved to background // includes switch to other app // includes just moving to task manager } } @ddinorahtovar
  11. Lifecycle Events class AppOwner() : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStartApplication()

    { // App was resumed or started } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStopApplication() { // App has been moved to background // includes switch to other app // includes just moving to task manager } } @ddinorahtovar
  12. Lifecycle Events •You can start using this code adding this

    to your Application class •Using - ProcessLifecycleOwner ProcessLifecycleOwner.get().lifecycle.addObserver(AppOwner()) @ddinorahtovar
  13. Lifecycle Events •I have an example for this specific case

    @ddinorahtovar
  14. Lifecycle depends of more things •Especially Lifecycle and LifecycleOwner @ddinorahtovar

    •OnLifecycle Event •ViewTree 
 Lifecycle 
 Owner •Process 
 Lifecycle 
 Owner LifecycleOwner Lifecycle •State •Events
  15. LifecycleOwner •No need to change anything on Fragments or Activities

    •Give you change to get the lifecycle of the view @ddinorahtovar
  16. LifecycleOwner Fragment 
 
 
 
 Initialize Lifecycle Set View

    to ViewTreeLifecycleOwner Set View to ViewTreeViewModel 
 StoreOwner Set View to ViewTreeSavedState 
 RegistryOwner Init of LifecycleRegistry FragmentViewLifecycleOwner Reports lifecycle of view Reports accessors of ViewModelStoreOwner Reports accessors of SavedStateRegistryOwner @ddinorahtovar
  17. LifecycleRegistr y •Handle observers - addObserver() •Used on Fragments and

    Activities •Handle and dispatch events around the process of the activity and fragment @ddinorahtovar
  18. OnLifecycleEvent •Get’s the event on the lifecycle @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public

    @interface OnLifecycleEvent { Lifecycle.Event value(); } @ddinorahtovar
  19. ViewTreeLifecycleOwner •Reports the lifecycle for the given view. •Set the

    lifecycleOwner to manage the views •Get the lifecycleOwner of the view @ddinorahtovar
  20. LiveData and Flow

  21. LiveData and Observers •Especially Lifecycle and LifecycleOwner viewModel.loading.observe(this, { //

    Adding the liveData observer to the // observers list // Lifecycle.State.STARTED or Lifecycle.State.RESUMED }) @ddinorahtovar
  22. LiveData and Observers @MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull

    Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing != null) { return; } owner.getLifecycle().addObserver(wrapper); } @ddinorahtovar
  23. LiveData and Observers @MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull

    Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing != null) { return; } owner.getLifecycle().addObserver(wrapper); } @ddinorahtovar
  24. LiveData and Observers @MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull

    Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing != null) { return; } owner.getLifecycle().addObserver(wrapper); } @ddinorahtovar
  25. LiveData and Observers @MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull

    Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing != null) { return; } owner.getLifecycle().addObserver(wrapper); } @ddinorahtovar
  26. LiveData are cool, but they are not there yet •Is

    highly bound to @MainThread - no concept of suspend •Attached to Android •Collection attached to high level components - Activities, Fragments, Dialogs @ddinorahtovar
  27. LiveData are cool, but they are not there yet Architecture

    make this a little 
 more complex 
 Repository ViewModel UI Android is welcome here Only Kotlin Flows LiveData @ddinorahtovar
  28. LiveData are cool, but they are not there yet class

    Event<out T>(private val content: T) { var hasBeenHandled = false private set fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } fun peekContent(): T = content } class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T >> { override fun onChanged(event: Event<T>?) { event ?. getContentIfNotHandled() ?. let { value -> onEventUnhandledContent(value) } } } @ddinorahtovar
  29. LiveData are cool, but they are not there yet class

    Event<out T>(private val content: T) { var hasBeenHandled = false private set fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } fun peekContent(): T = content } class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T >> { override fun onChanged(event: Event<T>?) { event ?. getContentIfNotHandled() ?. let { value -> onEventUnhandledContent(value) } } } @ddinorahtovar
  30. Flows are kinda cool! •Flow solve many problems for the

    part of architecture, making Repositories amicable •Still, collection on UI is complex •Flow does not have flow.value() •Flow is not aware of Lifecycles! @ddinorahtovar
  31. StateFlow and SharedFlow Name Type SharedFlow Hot Characteristic Emits to

    all the collectors •These two flows solve tons of problems Exists without collectors Replay cache StateFlow Hot SharedFlow that is read-only state Exists without collectors Conflated values @ddinorahtovar
  32. SharedFlow and StateFlow •But we have a problem 
 SharedFlow

    or StateFlow Activity Collection ViewModel @ddinorahtovar
  33. SharedFlow and StateFlow 
 SharedFlow/StateFlow 
 ViewModel val uiState: MutableStateFlow<UIState>

    = MutableStateFlow(UIState.Loading(false)) Activity Collection @ddinorahtovar
  34. SharedFlow and StateFlow 
 SharedFlow/StateFlow 
 Activity Collection ViewModel private

    fun setObservables() = lifecycleScope.launch { // CoroutineScope tied to the LifecycleOwner and lifecycle // Scope will be cancelled when lifecycle got destroyed } @ddinorahtovar
  35. SharedFlow and StateFlow 
 SharedFlow/StateFlow 
 Collection ViewModel private fun

    setObservables() = lifecycleScope.launch { // Awareness of the lifecycle viewModel.uiState.collect { // Collect the flow } } Activity @ddinorahtovar
  36. SharedFlow and StateFlow 
 SharedFlow/StateFlow 
 Collection ViewModel Activity •This

    only works half the times •buffer •conflate •flowOn •shareIn @ddinorahtovar
  37. SharedFlow and StateFlow 
 SharedFlow/StateFlow 
 // Coroutine listening for

    UI states private var job: Job? = null override fun onResume() { super.onResume() job = lifecycleScope.launch { viewModel.uiState.collect { // Collect the flow } } } override fun onStop() { job ? . cancel() super.onStop() } Activity ViewModel Collection @ddinorahtovar
  38. SharedFlow and StateFlow @ddinorahtovar

  39. StateFlow and SharedFlow collection

  40. SharedFlow and StateFlow collection •What do we really need? @ddinorahtovar

    •Launch and Run in specific stage •Cancel when is not longer in a stage •Automatic relaunch and cancellation
  41. SharedFlow and StateFlow collection @ddinorahtovar Safe collection Using a lib

    
 addRepeatingJob() repeatOnLifecycle() flowWithLifecycle() •It’s on alpha, but it works implementation: "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01"
  42. addRepeatingJob() @ddinorahtovar Safe collection Using a lib 
 repeatOnLifecycle() private

    fun setObservables() = viewLifecycleOwner.addRepeatingJob(Lifecycle.State.STARTED) { viewModel.uiHomeState.collect { // Collect the flow } } flowWithLifecycle() addRepeatingJob()
  43. Under the hood of: addRepeatingJob() @ddinorahtovar public fun LifecycleOwner.addRepeatingJob( state:

    Lifecycle.State, coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> Unit ): Job = lifecycleScope.launch(coroutineContext) { lifecycle.repeatOnLifecycle(state, block) }
  44. Under the hood of: addRepeatingJob() @ddinorahtovar public fun LifecycleOwner.addRepeatingJob( state:

    Lifecycle.State, coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> Unit ): Job = lifecycleScope.launch(coroutineContext) { lifecycle.repeatOnLifecycle(state, block) }
  45. Under the hood of: addRepeatingJob() @ddinorahtovar public fun LifecycleOwner.addRepeatingJob( state:

    Lifecycle.State, coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> Unit ): Job = lifecycleScope.launch(coroutineContext) { lifecycle.repeatOnLifecycle(state, block) }
  46. Under the hood of: addRepeatingJob() @ddinorahtovar public fun LifecycleOwner.addRepeatingJob( state:

    Lifecycle.State, coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> Unit ): Job = lifecycleScope.launch(coroutineContext) { lifecycle.repeatOnLifecycle(state, block) }
  47. repeatOnLifecycle() @ddinorahtovar Safe collection Using a lib 
 repeatOnLifecycle() flowWithLifecycle()

    private fun setObservables() = lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiHomeState.collect { // Safe collect } } } addRepeatingJob()
  48. Under the hood of: repeatOnLifecycle() @ddinorahtovar public suspend fun Lifecycle.repeatOnLifecycle(

    state: Lifecycle.State, block: suspend CoroutineScope.() - > Unit ) { require(state ! == Lifecycle.State.INITIALIZED) { "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state." } if (currentState = == Lifecycle.State.DESTROYED) { return } coroutineScope { withContext(Dispatchers.Main.immediate) { if (currentState === Lifecycle.State.DESTROYED) return@withContext var launchedJob: Job? = null var observer: LifecycleEventObserver? = null } } }
  49. Under the hood of: repeatOnLifecycle() @ddinorahtovar public suspend fun Lifecycle.repeatOnLifecycle(

    state: Lifecycle.State, block: suspend CoroutineScope.() - > Unit ) { require(state ! == Lifecycle.State.INITIALIZED) { "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state." } if (currentState = == Lifecycle.State.DESTROYED) { return } coroutineScope { withContext(Dispatchers.Main.immediate) { if (currentState === Lifecycle.State.DESTROYED) return@withContext var launchedJob: Job? = null var observer: LifecycleEventObserver? = null } } }
  50. Under the hood of: repeatOnLifecycle() @ddinorahtovar try { suspendCancellableCoroutine<Unit> {

    cont -> val startWorkEvent = Lifecycle.Event.upTo(state) val cancelWorkEvent = Lifecycle.Event.downFrom(state) observer = LifecycleEventObserver { _, event -> if (event == startWorkEvent) { launchedJob = this@coroutineScope.launch(block = block) return@LifecycleEventObserver } if (event == cancelWorkEvent) { launchedJob ?. cancel() launchedJob = null } if (event == Lifecycle.Event.ON_DESTROY) { cont.resume(Unit) } } this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver) } } finally { ?.
  51. == launchedJob = this@coroutineScope.launch(block = block) return@LifecycleEventObserver } if (event

    == cancelWorkEvent) { launchedJob ? . cancel() launchedJob = null } if (event == Lifecycle.Event.ON_DESTROY) { cont.resume(Unit) } } this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver) } } finally { launchedJob ?. cancel() observer ?. let { this@repeatOnLifecycle.removeObserver(it) } } Under the hood of: repeatOnLifecycle() @ddinorahtovar
  52. flowWithLifecycle() @ddinorahtovar viewModel.uiHomeState.flowWithLifecycle( lifecycle, Lifecycle.State.STARTED ) { // Safe collect

    } Safe collection Using a lib 
 addRepeatingJob() repeatOnLifecycle() flowWithLifecycle()
  53. Under the hood of: flowWithLifecycle() @ddinorahtovar @OptIn(ExperimentalCoroutinesApi : : class)

    public fun <T> Flow<T>.flowWithLifecycle( lifecycle: Lifecycle, minActiveState: Lifecycle.State = Lifecycle.State.STARTED ): Flow<T> = callbackFlow { lifecycle.repeatOnLifecycle(minActiveState) { this@flowWithLifecycle.collect { send(it) } } close() }
  54. Under the hood of: flowWithLifecycle() @ddinorahtovar @OptIn(ExperimentalCoroutinesApi : : class)

    public fun <T> Flow<T>.flowWithLifecycle( lifecycle: Lifecycle, minActiveState: Lifecycle.State = Lifecycle.State.STARTED ): Flow<T> = callbackFlow { lifecycle.repeatOnLifecycle(minActiveState) { this@flowWithLifecycle.collect { send(it) } } close() } Cold flow •With elements that are sent to a SendChannel with a block -> ProducerScope. •It allows elements to be produced by code that is running in a different context or concurrently
  55. Under the hood of: flowWithLifecycle() @ddinorahtovar @OptIn(ExperimentalCoroutinesApi : : class)

    public fun <T> Flow<T>.flowWithLifecycle( lifecycle: Lifecycle, minActiveState: Lifecycle.State = Lifecycle.State.STARTED ): Flow<T> = callbackFlow { lifecycle.repeatOnLifecycle(minActiveState) { this@flowWithLifecycle.collect { send(it) } } close() }
  56. It’s kinda cool! Give it a try

  57. LifecycleOwners and Flows: Cleaning the UI Dinorah Tova r Google

    Developer Expert Platform Mobile Engineer
 @ konfío.mx @ddinorahtovar @ddinorahtovar