Android/RxJava, prevent multiple subscriptions of the same kind

2.3k views Asked by At

Good morning, I am recently taken up playing around with RxJava and I am facing a problem.

I have a Fragment in a ViewPager which on onViewCreated() launches an HTTP request (handled by Retrofit and RxJava) to retrieve a list of Strings. The way it does it is by creating a subscription and adding it to a CompositeSubscription object

here is the code

final Subscription subscription = MyManager.getService().getListStringsFromWeb()
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<List<String>() {
                @Override
                public void onCompleted() {
                    // DO NOTHING
                }

                @Override
                public void onError(Throwable e) {
                    // DO SOMETHING
                }

                @Override
                public void onNext(List<String> result) {
                    returnResultToView(result);
                }
            });
addSubscription(subscription);

In the Fragment on onViewCreated() I check for the result being there, if there is not we call the method above, if there is we are happy as a clam.

The problem is that when I rotate the device the "first" subscription has yet to return, therefore the data is not remembered by the fragment and it launches another search, and if I keep rotating I keep adding subscriptions

I would like to know if there is a way to hold on the first subscription and when its result arrives just give it back to the Fragment and disregarding all the other subscriptions

STEP BY STEP DESIRED BEHAVIOR:

  • Launch Fragment and request
  • Screen shows loading image and background the subscription/request is launched
  • Multiple screen rotation holding the loading image since the first request is still pending
  • Once the result of the first call comes back return it to the fragment, and hence hide loading image and show result

Thank you very much

1

There are 1 answers

11
DeeV On BEST ANSWER

The unfortunate thing regarding Fragments and RxJava is that you have to keep the observable around somehow. One method is to make them static to the Fragment so that you only create them once, then subscribe/unsubscribe in onStart()/onStop() or however you decide.

The alternate approach if you don't like making static variables is to use a retained Fragment which handles the data. The View Fragment which shoes the data can then "bind" itself to the data Fragment. The advantages of this method is that the data is easily available to all components of an Activity, it abstracts away the data from the view for easy testing, and it outlives lifecycle components.

So for a retained fragment you'd do something like this:

public class DataFragment extends Fragment {

   private Observable<List<String> stringObs;

   @Override
   public void onAttach(Context ctx) {
      super.onAttach(ctx);
      setRetainInstance(true);
   }

   @Override
   public void onCreateView(LayoutInflater inflater, ViewGroup group, Bundle savedInsatnceState) {
      return null; // no view, no problem.
   }

   public Observable<List<String>> getStrings() {
     if (stringsObs == null) {
         stringsObs = MyManager.getService()
            .getListStringsFromWeb()
            .cache()
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread());
     }
     return stringsObs;
   }
}

Add the DataFragment to the FragmentManager like so:

FragmentManager fm = getFragmentManager();
DataFragment df = fm.findFragmentByTag("DataFragment");
if (df == null) {
   // Only add the retained fragment if it doesn't exist.  Otherwise, use the old one.
   fm.beginTransaction()
   .add(new DataFragment(), "DataFragment")
   .commit();
}

Then on the ViewFragment you can subscribe to it similarly like this:

public class ViewFragment extends Fragment {

   private Subscription s;

   @Override
   public void onStart() {
     super.onStart();
     DataFragment dataFragment = (DataFragment) getFragmentManager().findFragmentByTag("DataFragment");
     s = dataFragment.getStrings()
            .subscribe(new Observer<List<String>() {
              // Do things
            });
   }

   @Override
   public void onStop() {
      super.onStop();
      s.unsubscribe();
   }
}

Now, your ViewFragment can disappear and re-appear on a whim without obstructing the download. It's easier to manipulate the Observables in ways that you need as well.