Reputation: 1055
In my application I return LiveData from Room database (SQLite) in repository, and observe the data on my application Activity.
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
The method looks like this in repository:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId); //this part finishes after reuturn
});
return tourWithAllGeoPoints; //this part returns
}
mIsFirstTime checks if the Activity (or Fragment) is loading first time or not (if Bundle is null or not).
databaseWriteExecutor.execute() is a ThreadPool executing the code in own thread.
toursDAO.getTourWithAllGeoPoints(tourId) is where I ask and get data from Room database. It returns a LiveData object.
In Activity code I do observe the LiveData:
activeTourViewModel.getTourWithAllGeoPoints(tourId, mIsFirstTime).observe(this, geoPointsPlanned -> {
//Some code here changing UI, other variables, etc.
}
But the problem is that the method returns 'tourWithAllGeoPoints' before the execute() part is finished. So this means it returns an empty LiveData. Or the LiveData we observe on MainActivity is not the same LiveData we get from toursDAO.
And so in Activity it observes the empty LiveData.
My attempted solutions are:
1) I can run the query in main thread like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId);
return tourWithAllGeoPoints;
}
But then it gives warning message about not to run queries to Room database on main thread as it may take long time.
2) Or I can make the toursDAO.getTourWithAllGeoPoints(tourId) return a TourWithAllGeoPoints object rather than a LiveData, and put it into a LiveDataobject, like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
TourWithAllGeoPoints twagp = toursDAO.getTourWithAllGeoPoints(tourId);
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
So that it observes the changes in LiveData. But then I can't observe the changes made in database, since it just returns a List. This means I have to run the same method every time I make a change in the database.
3) Or I can put a LiveData inside a LiveData, also like this:
public LiveData<LiveData<TourWithAllGeoPoints>> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
LiveData<TourWithAllGeoPoints> twagp = toursDAO.getTourWithAllGeoPoints(tourId); //returns LiveData
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
But I don't know if putting LiveData inside a LiveData is a good idea or not.
Or the are other solutions. But how can I solve this problem?
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
Upvotes: 0
Views: 255
Reputation: 16534
For the specific problem you described (i.e. returning the first TourWithAllGeoPoints
and nothing else), it seems LiveData
isn't the most appropriate data type you can use here. LiveData
is meant to be used when, as the name says, the underlying data is live and it could change anytime, and you need to observe the data everytime it changes. If all you need is one value, it's better not to use LiveData
at all. Just make your DAO method getTourWithAllGeoPoints
return TourWithAllGeoPoints
(without LiveData
) and call it from a background thread. Take a look at this link for some ways to do that. It's much easier to use Kotlin coroutines in this case, but you'd need to be using Kotlin for that (which I recommend :) ).
But if the problem you described is generic (not exactly just for returning one value once), you can use a MediatorLiveData
to observe a LiveData
and post something different (or not) every time it emits a new value. Take a look at this code:
private MediatorLiveData<TourWithAllGeoPoints> mediator;
public YourRepositoryConstructor() {
mediator = new MediatorLiveData<>();
mediator.addSource(toursDAO.getTourWithAllGeoPoints(tourId), data -> {
if (mediator.getValue() != null) {
mediator.setValue(data);
}
});
return mediator;
}
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
return mediator;
}
A MediatorLiveData
observes one (or many) other LiveData
objects and emits a new value according to the changes of the other LiveData
objects. It may emit a different data type (i.e. it doesn't have to be the same type of the underlying LiveData
objects) or even not emit anything at all. It's all according to your MediatorLiveData
code. In this case specifically, every time the result of getTourWithAllGeoPoints
emits something new, you MediatorLiveData
will react to that and only emit a new value in itself if it's the first time. It can do that by checking if it's value is null
, it doesn't need the variable mIsFirstTime
(unless null
is a valid value for you in that case).
The MediatorLiveData
is a more generic approach suitable for the type of scenario you described, but it may be too much effort if you only need one result for that query, which you might solve by not using LiveData
at all.
Upvotes: 1