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 LiveDataDavid 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