Reputation: 45
Im working with the in-memory database, and doing some simple tasks like writing in some movies, then reading them out and displaying them. Im using RoomPersistance and i have some repositories set up. My problem:
Here i am getting the movies from a response, and inserting them in a database through the insertMovie method.
for (OMDBItem movie : response.body().getItems())
{
omdbItemRepository.insertMovie(movie);
}
This method looks like this:
public void insertMovie(OMDBItem movie){
AsyncTask<Void,Void,Void> atask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
movieDataBase.movieDao().insert(movie);
return null;
}
}.execute();
}
then i have this piece of code:
for (OMDBItem movie : response.body().getItems())
{
omdbItemRepository.insertMovie(movie);
}
lista_filmovi=omdbItemRepository.getAllMovies();
and getAllMovies() is a similar method that looks like this:
public List<OMDBItem> getAllMovies(){
new AsyncTask<Void,Void,Void>(){
@Override
protected Void doInBackground(Void... voids) {
lista_filmovi=movieDataBase.movieDao().getAllMovies();
return null;
}
}.execute();
return lista_filmovi;
}
The problem is that sometimes, this method getAllMovies returns me the movies i want, but sometimes it just returns null. And it only returns movies when i put some break-points and run it in the debugger. My quess is that by running it in the debugger and clicking though the methods, im giving the insertMovie(movie) AsyncTasks more time to do its job, and when getAllMovies() gets called, it gives me a good result. So basically the question is, is there anyway i can make the getAllMovies() AsyncTask not start until the insertMovie() AsyncTasks have finished. I know i can maybe put an onPostExecute in insertMovie(), but i want these methods sepereated ( i dont want to call getAllMovies() everytime after insertMovie()). Any solution?
Upvotes: 0
Views: 44
Reputation: 530
You have a few problems in your code.
The first is that you need to wait for all the movies to be written in the database to start the reading back. Then in the reading you cannot just return the value of lista_filmovi
as the reading will be async so the returned value will not be there when you will try to read it.
An example Async task to write movies could be:
public static class InsertMovie extends AsyncTask<OMDBItem,Void,Void> {
private final Database movieDataBase;
public InsertMovie(Database movieDataBase) {
this.movieDataBase = movieDataBase;
}
@Override
protected Void doInBackground(OMDBItem... movies) {
for (OMDBItem movie : movies)
movieDataBase.movieDao().insert(movie);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
// data is stored
}
}
To write the movies use the statement:
new InsertMovie(movieDataBase).execute(movies);
You shall not attempt to read the data until the OnPostExecute
is called. There are various ways to do that but the simpler could be to just start the reading there.
And then to read it back:
public static class GetAllMovies extends AsyncTask<Void,Void,List<OMDBItem>> {
private final Database movieDataBase;
public GetAllMovies(Database movieDataBase) {
this.movieDataBase = movieDataBase;
}
@Override
protected List<OMDBItem> doInBackground(Void... voids) {
return movieDataBase.movieDao().getAllMovies();
}
@Override
protected void onPostExecute(List<OMDBItem> allMovies) {
// post the result to your activity
}
}
Again the result will be available in the OnPostExecute
and you can't access it before that method is called.
The best ways to fit this in your Activity then varies. I suggest using an AndroidViewModel
and get the result as notifications on LiveData
objects. In this case you do not even need to use AsyncTask as you can just post the results in the LiveData
.
Start from an AndroidViewModel
like this:
/** ViewModel providing additional features to ease Room DB access */
public class RoomViewModel extends AndroidViewModel {
/** Thread executing Room operations */
private static class RoomThread extends Thread {
/** Queue of tasks pending execution */
private BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>();
/** Set to false to stop */
private boolean running = true;
/** Send this to stop the execution */
private Runnable STOP = new Runnable() {
@Override
public void run() {
running = false;
}
};
@Override
public void run()
{
while (running) {
try {
// execute next in line, when available
tasks.take().run();
} catch (InterruptedException e) {
// this should not happen
return;
}
}
}
}
/** Executes backround Room requests in order */
private RoomThread roomThread = new RoomThread();
public RoomViewModel(@NonNull Application application) {
super(application);
// start the background execution thread
roomThread.start();
}
/**
* Queues the specified Runnable for execution
* @param runnable The Runnable to be executed in background
*/
protected void execute(Runnable runnable)
{
roomThread.tasks.offer(runnable);
}
@Override
protected void onCleared() {
// queue the stop request
execute(roomThread.STOP);
super.onCleared();
}
}
This helps you as you will have just a single background thread for DB access and so the operations will be ordered.
In your MovieViewModel
extending RoomViewModel
you can then use:
// write the movies
execute(new Runnable() {
@Override
public void run() {
for (OMDBItem movie : movies) movieDataBase.movieDao().insert(movie);
}
});
and
// read the movies
execute(new Runnable() {
@Override
public void run() {
allMovies.postValue(movieDataBase.movieDao().getAllMovies());
}
});
In the Activity you can observe the allMovies
as MutableLiveData<List<OMDBItem>>
and get notification on when new data is available to show it.
Upvotes: 1