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!

Dinorah Tovar

May 04, 2021
Tweet

More Decks by Dinorah Tovar

Other Decks in Technology

Transcript

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

    Developer Expert Platform Mobile Engineer
 @ konfío.mx @ddinorahtovar @ddinorahtovar
  2. Lifecycle depends of more things •OnLifecycle Event •ViewTree 
 Lifecycle

    
 Owner •Process 
 Lifecycle 
 Owner LifecycleOwner Lifecycle •State •Events •Especially Lifecycle and LifecycleOwner @ddinorahtovar
  3. 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
  4. 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
  5. Lifecycle Events •Events - can be used on regular classes

    •To track OnResume and OnPause •To track NetworkState @ddinorahtovar
  6. 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
  7. 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
  8. 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
  9. Lifecycle Events •You can start using this code adding this

    to your Application class •Using - ProcessLifecycleOwner ProcessLifecycleOwner.get().lifecycle.addObserver(AppOwner()) @ddinorahtovar
  10. Lifecycle depends of more things •Especially Lifecycle and LifecycleOwner @ddinorahtovar

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

    •Give you change to get the lifecycle of the view @ddinorahtovar
  12. 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
  13. LifecycleRegistr y •Handle observers - addObserver() •Used on Fragments and

    Activities •Handle and dispatch events around the process of the activity and fragment @ddinorahtovar
  14. ViewTreeLifecycleOwner •Reports the lifecycle for the given view. •Set the

    lifecycleOwner to manage the views •Get the lifecycleOwner of the view @ddinorahtovar
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. SharedFlow and StateFlow •But we have a problem 
 SharedFlow

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

    = MutableStateFlow(UIState.Loading(false)) Activity Collection @ddinorahtovar
  28. 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
  29. SharedFlow and StateFlow 
 SharedFlow/StateFlow 
 Collection ViewModel private fun

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

    only works half the times •buffer •conflate •flowOn •shareIn @ddinorahtovar
  31. 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
  32. 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
  33. 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"
  34. addRepeatingJob() @ddinorahtovar Safe collection Using a lib 
 repeatOnLifecycle() private

    fun setObservables() = viewLifecycleOwner.addRepeatingJob(Lifecycle.State.STARTED) { viewModel.uiHomeState.collect { // Collect the flow } } flowWithLifecycle() addRepeatingJob()
  35. 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) }
  36. 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) }
  37. 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) }
  38. 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) }
  39. 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()
  40. 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 } } }
  41. 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 } } }
  42. 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 = [email protected](block = block) return@LifecycleEventObserver } if (event == cancelWorkEvent) { launchedJob ?. cancel() launchedJob = null } if (event == Lifecycle.Event.ON_DESTROY) { cont.resume(Unit) } } [email protected](observer as LifecycleEventObserver) } } finally { ?.
  43. == launchedJob = [email protected](block = block) return@LifecycleEventObserver } if (event

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

    } Safe collection Using a lib 
 addRepeatingJob() repeatOnLifecycle() flowWithLifecycle()
  45. 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) { [email protected] { send(it) } } close() }
  46. 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) { [email protected] { 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
  47. 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) { [email protected] { send(it) } } close() }
  48. LifecycleOwners and Flows: Cleaning the UI Dinorah Tova r Google

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