Coder88
Coder88

Reputation: 1055

Problems returning LiveData in Activity that observes changes in database using MVVM model

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

Answers (1)

cd1
cd1

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

Related Questions