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

    View full-size slide

  2. Let’s discuss
    LifecycleOwners

    View full-size slide

  3. Everything has lifecycle
    @ddinorahtovar

    General Application
    Activity Fragment
    •Defines an object that has an Android Lifecycle
    Classes

    View full-size slide

  4. Lifecycle depends of more things
    •OnLifecycle
    Event

    •ViewTree

    Lifecycle

    Owner

    •Process

    Lifecycle

    Owner
    LifecycleOwner
    Lifecycle
    •State

    •Events
    •Especially Lifecycle and LifecycleOwner
    @ddinorahtovar

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  7. Lifecycle Events
    •Events - can be used on regular classes
    •To track OnResume and OnPause
    •To track NetworkState
    @ddinorahtovar

    View full-size slide

  8. ProcessLifecycleOwner
    •Provides lifecycle for the whole application
    process

    General Application
    Activity
    Fragment
    Dialog
    @ddinorahtovar

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  12. Lifecycle Events
    •You can start using this code adding this to
    your Application class
    •Using - ProcessLifecycleOwner
    ProcessLifecycleOwner.get().lifecycle.addObserver(AppOwner())
    @ddinorahtovar

    View full-size slide

  13. Lifecycle Events
    •I have an example for this specific case
    @ddinorahtovar

    View full-size slide

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

    •ViewTree

    Lifecycle

    Owner

    •Process

    Lifecycle

    Owner
    LifecycleOwner
    Lifecycle
    •State

    •Events

    View full-size slide

  15. LifecycleOwner
    •No need to change anything on Fragments or
    Activities
    •Give you change to get the lifecycle of the view
    @ddinorahtovar

    View full-size slide

  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

    View full-size slide

  17. LifecycleRegistr
    y

    •Handle observers - addObserver()
    •Used on Fragments and Activities
    •Handle and dispatch events around the process of
    the activity and fragment
    @ddinorahtovar

    View full-size slide

  18. OnLifecycleEvent
    •Get’s the event on the lifecycle
    @Retention(RetentionPolicy.RUNTIME)

    @Target(ElementType.METHOD)

    public @interface OnLifecycleEvent {

    Lifecycle.Event value();

    }
    @ddinorahtovar

    View full-size slide

  19. ViewTreeLifecycleOwner
    •Reports the lifecycle for the given view.
    •Set the lifecycleOwner to manage the views
    •Get the lifecycleOwner of the view
    @ddinorahtovar

    View full-size slide

  20. LiveData and Flow

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  28. LiveData are cool, but they are not there yet
    class Event(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(private val onEventUnhandledContent: (T)
    ->
    Unit) : Observer>>
    {

    override fun onChanged(event: Event?) {

    event
    ?.
    getContentIfNotHandled()
    ?.
    let { value
    ->


    onEventUnhandledContent(value)

    }

    }

    }
    @ddinorahtovar

    View full-size slide

  29. LiveData are cool, but they are not there yet
    class Event(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(private val onEventUnhandledContent: (T)
    ->
    Unit) : Observer>>
    {

    override fun onChanged(event: Event?) {

    event
    ?.
    getContentIfNotHandled()
    ?.
    let { value
    ->


    onEventUnhandledContent(value)

    }

    }

    }
    @ddinorahtovar

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  32. SharedFlow and StateFlow
    •But we have a problem

    SharedFlow or StateFlow
    Activity Collection ViewModel
    @ddinorahtovar

    View full-size slide

  33. SharedFlow and StateFlow

    SharedFlow/StateFlow

    ViewModel
    val uiState: MutableStateFlow =

    MutableStateFlow(UIState.Loading(false))
    Activity
    Collection
    @ddinorahtovar

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  36. SharedFlow and StateFlow

    SharedFlow/StateFlow

    Collection
    ViewModel
    Activity
    •This only works half the times

    •buffer

    •conflate

    •flowOn

    •shareIn
    @ddinorahtovar

    View full-size slide

  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

    View full-size slide

  38. SharedFlow and StateFlow
    @ddinorahtovar

    View full-size slide

  39. StateFlow and
    SharedFlow collection

    View full-size slide

  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

    View full-size slide

  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"

    View full-size slide

  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()

    View full-size slide

  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)

    }

    View full-size slide

  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)

    }

    View full-size slide

  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)

    }

    View full-size slide

  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)

    }

    View full-size slide

  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()

    View full-size slide

  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

    }

    }

    }

    View full-size slide

  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

    }

    }

    }

    View full-size slide

  50. Under the hood of: repeatOnLifecycle()
    @ddinorahtovar
    try {

    suspendCancellableCoroutine { 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 {

    ?.

    View full-size slide

  51. ==
    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

    View full-size slide

  52. flowWithLifecycle()
    @ddinorahtovar
    viewModel.uiHomeState.flowWithLifecycle(

    lifecycle, Lifecycle.State.STARTED

    ) {

    //
    Safe collect

    }
    Safe collection

    Using a lib


    addRepeatingJob()
    repeatOnLifecycle()
    flowWithLifecycle()

    View full-size slide

  53. Under the hood of: flowWithLifecycle()
    @ddinorahtovar
    @OptIn(ExperimentalCoroutinesApi
    : :
    class)

    public fun Flow.flowWithLifecycle(

    lifecycle: Lifecycle,

    minActiveState: Lifecycle.State = Lifecycle.State.STARTED

    ): Flow = callbackFlow {

    lifecycle.repeatOnLifecycle(minActiveState) {

    [email protected] {

    send(it)

    }

    }

    close()

    }

    View full-size slide

  54. Under the hood of: flowWithLifecycle()
    @ddinorahtovar
    @OptIn(ExperimentalCoroutinesApi
    : :
    class)

    public fun Flow.flowWithLifecycle(

    lifecycle: Lifecycle,

    minActiveState: Lifecycle.State = Lifecycle.State.STARTED

    ): Flow = 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

    View full-size slide

  55. Under the hood of: flowWithLifecycle()
    @ddinorahtovar
    @OptIn(ExperimentalCoroutinesApi
    : :
    class)

    public fun Flow.flowWithLifecycle(

    lifecycle: Lifecycle,

    minActiveState: Lifecycle.State = Lifecycle.State.STARTED

    ): Flow = callbackFlow {

    lifecycle.repeatOnLifecycle(minActiveState) {

    [email protected] {

    send(it)

    }

    }

    close()

    }

    View full-size slide

  56. It’s kinda cool!


    Give it a try

    View full-size slide

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

    Google Developer Expert
    Platform Mobile Engineer

    @ konfío.mx
    @ddinorahtovar
    @ddinorahtovar

    View full-size slide