I have a separate class in which I handle data fetching (specifically Firebase) and I usually return LiveData objects from it and update them asynchronously. Now I want to have
In ViewModel documentation
However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects.
Another way is for the data to implement RxJava rather than LiveData, then it won't have the benefit of being lifecycle-aware.
In google sample of todo-mvvm-live-kotlin, it uses a callback without LiveData in ViewModel.
I am guessing if you want to comply with the whole idea of being lifecycle-ware, we need to move observation code in Activity/Fragment. Else, we can use callback or RxJava in ViewModel.
Another compromise is implement MediatorLiveData (or Transformations) and observe (put your logic here) in ViewModel. Notice MediatorLiveData observer won't trigger (same as Transformations) unless it's observed in Activity/Fragment. What we do is we put a blank observe in Activity/Fragment, where the real work is actually done in ViewModel.
// ViewModel
fun start(id : Long) : LiveData<User>? {
val liveData = MediatorLiveData<User>()
liveData.addSource(dataSource.getById(id), Observer {
if (it != null) {
// put your logic here
}
})
}
// Activity/Fragment
viewModel.start(id)?.observe(this, Observer {
// blank observe here
})
PS: I read ViewModels and LiveData: Patterns + AntiPatterns which suggested that Transformations. I don't think it work unless the LiveData is observed (which probably require it to be done at Activity/Fragment).
Use Kotlin coroutines with Architecture components.
You can use the liveData
builder function to call a suspend
function, serving the result as a LiveData
object.
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
You can also emit multiple values from the block. Each emit()
call suspends the execution of the block until the LiveData
value is set on the main thread.
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
In your gradle config, use androidx.lifecycle:lifecycle-livedata-ktx:2.2.0
or higher.
There is also an article about it.
Update: Also it's possible to change LiveData<YourData>
in the Dao
interface
. You need to add the suspend
keyword to the function:
@Query("SELECT * FROM the_table")
suspend fun getAll(): List<YourData>
and in the ViewModel
you need to get it asynchronously like that:
viewModelScope.launch(Dispatchers.IO) {
allData = dao.getAll()
// It's also possible to sync other data here
}
I think you can use observeForever which does not require the lifecycle owner interface and you can observe results from the viewmodel
However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects.
In this Github issue, he describes that the situations that be applied the above rule are that observed lifecycle-aware observables are hosted by another lifecycle scope.
There is no problem that observes LiveData
in ViewModel
contains observed LiveData
.
class MyViewModel : ViewModel() {
private val myLiveData = MutableLiveData(1)
init {
viewModelScope.launch {
myLiveData.asFlow().collect {
// Do Something
}
}
}
}
class MyViewModel : ViewModel() {
private val myFlow = MutableStateFlow(1)
private val myLiveData = myFlow.asLiveData(viewModelScope.coroutineContext)
}
The asFlow
makes a flow that makes LiveData
activate at starting collect
. I think the solution with MediatorLiveData
or Transformations
and attaching a dummy observer doesn't have differences using the Flow
except for emitting value from LiveData
is always observed in the ViewModel
instance.
I know there have already been amazing answers for this topic, but I wanted to add my own as well:
If you want to stick to LiveData
you can always use Transformations.map
so that you don't have to observe
in the ViewModel
but rather only in the Fragment
/Activity
.
Otherwise, you can use SharedFlow
, a single event observable. For more, I have written an article here: https://coroutinedispatcher.com/posts/decorate_stateful_mvvm/
You don't have to pass viewLifecycleOwner
in the ViewModel
because there is no point in calling observe
in the ViewModel
when the View
just needs the latest result after all.
In this blog post by Google developer Jose Alcérreca it is recommended to use a transformation in this case (see the "LiveData in repositories" paragraph) because ViewModel shouldn't hold any reference related to View
(Activity, Context etc) because it made it hard to test.