Search code examples
javaandroidrestretrofitreactivex

Does my Retrofit/ReactiveX method actually retrieve data asynchronously?


swipeRefreshLayout.setOnRefreshListener(() -> {
 swipeRefreshLayout.setRefreshing(true);
 retrieveData(mCardAdapter, db);
});

For some reason the following method is blocking my main UI thread, but it should be running in the background. For example, the refresh indicator hangs when I run retrieveData(). If I initialize a progress dialog before running, it also hangs and I can't scroll through my RecyclerView. Am I fundamentally misunderstanding something here?

public void retrieveData(final CardAdapter mCardAdapter, SQLiteHelper db) {
 CausticRetrofitService service = ServiceFactory.createRetrofitService(CausticRetrofitService.class, CausticRetrofitService.SERVICE_ENDPOINT);
 service.getMedia()
  .subscribeOn(Schedulers.newThread())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(new Subscriber < MediaResponse > () {
   @Override
   public final void onCompleted() {

    Log.e("CausticRetrofitService", "Caustic Request Completed!");

    /* Cancel all progress indicators after data retrieval complete */
    setRefreshingFalse();

    // TODO: Add media to local data store and then display them one-by-one in real-time
    mCardAdapter.addData(db.getAllMediaImages()); // Add all media images to card views
    Log.d(getClass().toString(), "Added to local database: " + db.getAllMediaImages());
    mCardAdapter.notifyDataSetChanged();
   }

   @Override
   public final void onError(Throwable e) {
    /* Cancel all progress indicators on data retrieval error */
    setRefreshingFalse();

    Toast.makeText(getApplicationContext(), "Cannot retrieve data. Please try again later.", Toast.LENGTH_SHORT).show();
    Log.e("CausticRetrofitService", e.getMessage());
   }

   @Override
   public final void onNext(MediaResponse mediaResponse) {
    if (mediaResponse != null) {

     Log.e("CausticRetrofitService", "Returned objects: " + mediaResponse.getResults());

     for (String mediaId: mediaResponse.getResults()) {
      Log.e("CausticRetrofitService", mediaId);
     }

     List < String > mediaIds = mediaResponse.getResults();
     Log.d(getClass().toString(), "All Media IDs: " + mediaIds);

     if (mediaIds.isEmpty()) {
      Toast.makeText(getApplicationContext(), "Cannot retrieve data. Please try again later.", Toast.LENGTH_SHORT).show();
     }

     mCardAdapter.clear();
     mCardAdapter.notifyDataSetChanged();

     /* Store objects from remote web service to local database */
     for (String mediaId: mediaIds) {
      // TODO: Why are these null?
      Log.d(getClass().toString(), "Media Id: " + mediaId);
      MediaImage newMediaImage = new MediaImage();
      newMediaImage.setTitle(mediaId);
      db.addMediaImage(newMediaImage); // Add media image to local database
     }

    } else {
     Log.e("CausticRetrofitService", "Object returned is null.");
    }

   }

  });
}

I'm thinking that adding the remote data to the local data store in the onNext() method might be the thing that's blocking, although I'm not certain.


Solution

  • Your network call is done in a new thread as you specified, but the Subscriber methods onNext() and onComplete() runs on the observing Scheduler, which is the main thread.

    You seem to be doing some database operations on those, try to offload the caching also to the background thread using a doOnNext() operator.

    What doOnNext() will do, is that it is called for each emission in your stream.

    It can go something like that

    service.getMedia()
      .doOnNext(data -> cacheData(data))
      .subscribeOn(Schedulers.newThread())
      .observeOn(AndroidSchedulers.mainThread())
    

    Where cacheData() is a method that does all your DB calls. And the only things left in your onNext() and onComplete() would be updating the UI only.