Pasker
Pasker

Reputation: 45

Synchronizing AsyncTasks

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

Answers (1)

gxcare
gxcare

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

Related Questions