Reputation: 383
I'm new to LiveData and I've been doing some tests lately. I have an app where I need to display data that can be filtered (name, category, date...). The filters can be combined too (name + date). This data comes from an API call with Retrofit + RXJava.
I know that I can have the data directly on my view without using LiveData. However, I thought that it would be interesting to use a ViewModel + LiveData. First, to test how it works but also to avoid trying to set data if the view is not active (thanks to LiveData) and save the data in case of configuration changes (thanks to ViewModel). These were things that I had to handle manually before.
So the problem is that I didn't find a way to easily handle the filters with LiveData. In cases where the user chooses one filter I managed to make it work with switchMap:
return Transformations.switchMap(filter,
filter -> LiveDataReactiveStreams.fromPublisher(
repository.getData(filter).toFlowable(BackpressureStrategy.BUFFER)));
If he chooses two filters, I saw that I could use a custom MediatorLiveData and that's what I did. However, the problem here is that my repository call is done as many times as the number of filters I have and I can't set two filters at the same time.
My custom MediatorLiveData:
class CustomLiveData extends MediatorLiveData<Filter> {
CustomLiveData(LiveData<String> name, LiveData<String> category) {
addSource(name, name -> {
setValue(new Filter(name, category.getValue()));
});
addSource(category, category -> {
setValue(new Filter(name.getValue(), newCategory));
});
}
}
CustomLiveData trigger = new CustomLiveData(name, category);
return Transformations.switchMap(trigger,
filter -> LiveDataReactiveStreams.fromPublisher(
repository.getData(filter.getName(), filter.getCategory())
.toFlowable(BackpressureStrategy.BUFFER)));
Did I understand well the usage of MediatorLiveData? Is it possible to do what I'm trying to achieve with LiveData?
Thanks!
Upvotes: 8
Views: 7799
Reputation: 7965
The way you set this up in your answer, every time you call updateData()
you will be adding a new source to your MediatorLiveData
. Do you need the different sources every time a new name
and category
is changed?
Also, could you have your repository method getData
expose a LiveData
directly? And finally, would it be possible to use Kotlin instead of Java?
If so, you can simplify your flow by checking conditionally on the onChanged
of each source when to set the value in your data
MediatorLiveData.
For example, following the documentation here:
//Create a new instance of MediatorLiveData if you don't need to maintain the old sources
val data = MediatorLiveData<List<Data>>().apply {
addSource(repository.getData(name, category)) { value ->
if (yourFilterHere) {
this.value = value
}
}
}
You could also extend this idea to create an extension function with Kotlin:
inline fun <T> LiveData<T>.filter(crossinline filter: (T?) -> Boolean): LiveData<T> {
return MediatorLiveData<T>().apply {
addSource(this@filter) {
if (filter(it)) {
this.value = it
}
}
}
}
Then, in your updateData()
method you can do:
val data = repository.getData(name, category)
.filter { value ->
// Your boolean expression to validate the values to return
}
Upvotes: 7
Reputation: 383
I think I was seeing the problem from a wrong angle. Correct me if I'm wrong, please.
I was trying to update the LiveData based on the changes of other LiveDatas when I could update my LiveData directly. The solution I found is to have a MediatorLiveData that is updated directly by the view. It could be a MutableLiveData but as I'm using LiveDataReactiveStreams and it doesn't accept a MutableLiveData I didn't find another solution.
public class MainViewModel extends AndroidViewModel {
// Had to use a MediatorLiveData because the LiveDataReactiveStreams does not accept a MutableLiveData
private MediatorLiveData<List<Data>> data = new MediatorLiveData<>();
public MainViewModel(@NonNull Application application) {
super(application);
data.addSource(
LiveDataReactiveStreams.fromPublisher(
repository.getData(name, category)
.toFlowable(BackpressureStrategy.BUFFER)
), value -> data.setValue(value));
}
public LiveData<List<Data>> getData() {
return data;
}
public void updateData(String name, String category) {
data.addSource(
LiveDataReactiveStreams.fromPublisher(
repository.getData(name, category)
.toFlowable(BackpressureStrategy.BUFFER)
), value -> data.setValue(value));
}
}
And then on the activity I simply call it like this:
viewModel.updateData(name, category);
I don't know if I should remove a source before trying to add another but my API calls are only done once with this solution and I can have both filters.
Upvotes: 0