Skip to content

Latest commit

 

History

History
31 lines (24 loc) · 8.03 KB

README.md

File metadata and controls

31 lines (24 loc) · 8.03 KB

Kotlin_Flow_To_The_View

PoC using Flow to the View on an Android Project. DO NOT expose a Flow to the View. Use LiveData to communicate between ViewModel and View instead.

TL;DR :

Flow with .asLiveData()
or liveData {}
Flow with .stateIn() viewModelScope.launch() with MutableLiveData field
ViewModelScope ✔️ ✔️ ✔️
Lifecycle ✔️
Suspended after Activity is stopped for 5s (default)
✔️
Suspended after SharingStarted's timeout

Resources will be wasted (but no crash) as soon as Activity is stopped
Republish content on rebind ✔️ No ❌ Yes (need distinctUntilChanged() before collect on View) ✔️ No
Adjustable Timeout ✔️
timeoutInMs parameter
✔️
stopTimeoutMillis of SharingStarted.WhileSubscribed value for started parameter
On timeout (5s) behavior Restarts only if Flow didn't complete¹ Restarts only if Flow didn't complete¹
or always exposes initial state and restarts Flow (with replayExpirationMillis = 0)
Adjustable Initial state ✔️
Must expose initial state manually (can use latestValue to check if a previous value has been emitted)
✔️✔️
Initial state is exposed automatically when coroutine is restarted
Dynamic Initial state ✔️
Can change arbitrarily

Initial state can't change between timeouts
Unit Testing ✔️
Must inject Dispatchers
Must use TestCoroutineScope & InstantTaskExecutorRule
✔️
Must inject Dispatchers and SharingStarted strategy
Must use TestCoroutineScope
✔️
Must inject Dispatchers
Must use TestCoroutineScope & InstantTaskExecutorRule
Boilerplate ✔️✔️
2 lines:
field .asLiveData(dispatchers.io)
or liveData(dispatchers.io) {}
injected Dispatchers

4 to 6 lines:
.stateIn()
shared parameter
scope parameter
initial value parameter
injected Dispatchers
injected SharingStarted strategy

4 to 5 lines:
private MutableLiveData field
LiveData getter
init {}
viewModelScope.launch
injected Dispatchers
Complexity / Error prone ✔️ ✔️
LiveData dependency ✔️

¹ : If a hot flow (MutableStateFlow, MutableSharedFlow, Channel, etc...) is used to produce values downstream, the flow will never complete !
This means the collect will restart after the 5s timeout, even if it would seem unnecessary.
More information here

Using .asLiveData() extension, liveData() function or .stateIn() extension is a better approach than simply launching on the viewModelScope to publish to a MutableLiveData or MutableStateFlow. This is because when using .asLiveData(), liveData() or stateIn(), the coroutine is cancelled 5 seconds after leaving the Activity (not killing the application, just pressing 'home' or 'recent apps' button !), avoiding possibly unnecessary work.

There is one way to go with pure Kotlin, meaning there's no need for LiveData dependencies, but I wouldn't recommend it at the time, because any hot flow (see ¹) will make your collection restart after a timeout. Using a MutableLiveData with a switchMap() operator to "trigger" a flow is the only viable option to this day. Also, unit testing is a bit more complicated (one needs to inject both Dispatchers and SharingStarted strategy). And, to my opinion, it's more error prone.

See for yourself in the project, both "pure Kotlin" and LiveData approach are used !

Inspired by : https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda