David Ng
09/26/2020, 2:43 AMprivate val _status = MutableLiveData<Status>()
val status: LiveData<Status>
get = _status
fun getStatus(id: Int) {
viewModelScope.launch {
repository.getStatus(id) // This return Flow<Status>
.collect { _status = it}
}
}
And I use data binding on my layout
<TextView
android:text="@{viewModel.status}" />
The UI doesn't get updated to the latest value as the status come through from repository, the Main thread is being held up.
If I switch the coroutine to use Dispatcher.IO, I got "java.lang.IllegalStateException: Cannot invoke setValue on a background thread"
as the status need to be set on Main thread.
I've tried using BroadcastChannel
private val statusChannel = BroadcastChannel<Status>(Channel.CONFLATED)
val status = statusChannel.asFlow().asLiveData()
fun getStatus(id: Int) {
viewModelScope.launch(<http://Dispatcher.IO|Dispatcher.IO>) {
repository.getStatus(id) // This return Flow<Status>
.collect { statusChannel.offer(it) }
}
}
But the idea of converting the channel back to flow feels odd.
What's the best way to approach this?Karthikeyan1241997
09/26/2020, 3:08 AMDavid Ng
09/26/2020, 3:12 AM.postValue
can be used in non-UI thread. That's all I need, thanks!Karthikeyan1241997
09/26/2020, 3:16 AMwithContext(Dispatchers.Main)
since collect is a suspend function and directly set the value instead of postValueDavid Ng
09/26/2020, 3:18 AMIan Lake
09/26/2020, 3:55 AMpostValue()
will always skip a frame (it uses a technique that waits for vsync), while switching to Dispatchers.Main
will not necessarily do so (it isn't tied to vsync), making the later generally the better choiceDavid Ng
09/26/2020, 3:58 AMIan Lake
09/26/2020, 4:01 AMflatMapLatest
to transform that into your getStatus
Flow, then use asLiveData()
to convert that into a LiveData for your UI layerIan Lake
09/26/2020, 4:03 AMgetStatus
collect automatically when the ID changesIan Lake
09/26/2020, 4:03 AMgetStatus
twice, the previous collect will continue)David Ng
09/26/2020, 4:06 AMDavid Ng
09/26/2020, 4:06 AMIan Lake
09/26/2020, 4:10 AMIan Lake
09/26/2020, 4:14 AMBroadcastChannel
+ asFlow
approach for the ID is probably better than a MutableStateFlow
since MutableStateFlow
requires an initial state (which you could simulate with a filterNotNull
if you wanted)David Ng
09/26/2020, 4:17 AMMutableStateFlow
private val _status = MutableStateFlow<Status>()
val status: LiveData<Status>
get = _status.asLiveData()
fun getStatus(id: Int) {
viewModelScope.launch {
repository.getStatus(id) // This return Flow<Status>
.collect { _status = it}
}
}
And the repository layer would use .flatMapLatest
to emit the latest flow?David Ng
09/26/2020, 4:19 AMBroadcastChannel
generally the approach to this problem? Feels a little odd to convert it to Flow
again then LiveData
David Ng
09/26/2020, 4:22 AMMutableStateFlow
it won't build with the above code as MutableStateFlow<Status>()
is expecting a parameterIan Lake
09/26/2020, 4:27 AMprivate val idChannel = BroadcastChannel<Int>(Channel.CONFLATED)
val status = idChannel.asFlow().flatMapLatest { id ->
repository.getStatus(id)
}.asLiveData()
fun setId(id: Int) {
idChannel.offer(id)
}
David Ng
09/26/2020, 4:33 AMIan Lake
09/26/2020, 4:34 AMid
is an argument to your Fragment that doesn't change, you could make this even easier by using the Saved State module for ViewModel: https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate
class SavedStateViewModel(state: SavedStateHandle) : ViewModel() {
val status = state.getLiveData("id").asFlow().flatMapLatest { id ->
repository.getStatus(id)
}.asLiveData()
}
This takes advantage of the fact that the SavedStateHandle
is automatically populated by the arguments of the Fragment when you use Fragment 1.2.0 or higher, meaning you don't ever need to set the ID - it is automatically pulled from the arguments when the ViewModel is createdDavid Ng
09/26/2020, 4:42 AMSavedStateHandle
but didn't use it as I started off the project using safe args plugin. They seems to work differently. Is SavedStateHandle
the way forward to pass arguments?Ian Lake
09/26/2020, 4:43 AMgetLiveData()
is the same android:name
of your argument in your graph, they'll read the same valueIan Lake
09/26/2020, 4:44 AMDavid Ng
09/26/2020, 4:46 AMAidooyaw1992
06/29/2021, 5:21 PMAidooyaw1992
06/29/2021, 5:22 PM