16

Has anyone played around with Retrofit 2.0, specifically the Call.cancel() method?

When is the best time to trigger that? I have tried calling it in onStop() of a Fragment but have run into some issues with a call being cancelled when the screen display gets turned off. Also I tried calling it in onDestroy() of a Fragment but this method does not cancel a call that get triggered in a ViewPager ( for example switching between tabs)

Does anyone have a working example of this?

I have attempted to implement this my Loop repo : https://github.com/lawloretienne/Loop

VenomVendor
  • 15,064
  • 13
  • 65
  • 96
Etienne Lawlor
  • 6,817
  • 18
  • 77
  • 89

2 Answers2

11

The 'right' place will largely depend on your specific use-cases. As you've discovered, there's unlikely to be a one-size-fits-all solution. Here are a couple things to consider based on your stated needs:


Is cancellation of network requests when the screen is switched off a big problem for your app? Is a user likely to turn the screen off while expecting the app to continue functioning?

  • If not, you are safe to use onStop, as you've already described.
  • If so, you can move your network requests to a class that lives outside the Activity and Fragment lifecycles (e.g. using a singleton network request manager, or relying more on Service subclasses). You will then be able to handle cancellations on a case-by-case basis. For example, you would still be able to cancel requests inside lifecycle callbacks whenever you wanted to (by signaling this to the manager), but you wouldn't be required to.

With respect to cancelling network requests triggered by Fragments in a ViewPager, you might want to implement your own faux-lifecycle methods. Here's a great pattern I've used a couple of times. The gist is as follows:

  • Have all Fragments used by the ViewPager implement an interface that contains 'fake' versions of the lifecycle methods you care about.

Example:

public interface FragmentLifecycle {
    public void onStartFragment();
    public void onStopFragment();
}
  • Set an OnPageChangeListener on your ViewPager, and track the current page inside it. Whenever the page changes, call the appropriate methods on the incoming/outgoing fragments.

Example:

private OnPageChangeListener pageChangeListener = new OnPageChangeListener() {

    int currentPosition = 0;

    @Override
    public void onPageSelected(int newPosition) {
        final FragmentLifecycle fragmentToShow = (FragmentLifecycle) pageAdapter.getItem(newPosition);
        fragmentToShow.onStartFragment();

        final FragmentLifecycle fragmentToHide = (FragmentLifecycle)pageAdapter.getItem(currentPosition);
        // Cancel network requests inside this callback. It
        // corresponds to the current page moving off-screen.
        fragmentToHide.onStopFragment(); 

        currentPosition = newPosition;
    }

    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {
        // no-op
    }

    public void onPageScrollStateChanged(int arg0) {
        // no-op
    }
};

(I updated the linked example to use onStart/onStop since you already mentioned that lifecycle pair in your question.)

Hopefully that gives you some ideas as to how best to use the new cancellation features in Retrofit 2! Let us know what you come up with.

stkent
  • 19,772
  • 14
  • 85
  • 111
  • 1
    I have it working in `onDestroyView()` . https://gist.github.com/lawloretienne/5b8529f536bd0c5f72f7 But my problem is there are instances where a `NetworkOnMainThreadException` is thrown so that Call object never gets cancelled. This of course defeats the purpose of the cancelling mechanism if it can't happen every time. Apparently there is some kind of Strictmode issue in OKHttp. There is an issue filed here https://github.com/square/okhttp/issues/1592 – Etienne Lawlor Sep 25 '15 at 06:16
  • Yeah, I don't see a good way to avoid that until it's patched in OkHttp unfortunately. You'd need to fall back on a mechanism to manually ignore the results of calls that were supposed to be cancelled, but this is the same solution you would use without Call.cancel() anyway. – stkent Sep 25 '15 at 14:43
  • 2
    In the meantime I am wrapping `Call.cancel()` in an `AsyncTask`. https://github.com/square/okhttp/issues/1592#issuecomment-143309765 – Etienne Lawlor Sep 25 '15 at 18:13
1

Have you try UserVisibleHint?

   @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
                // fragment visible to user
        }else{
           // fragment invisible 
           // you can call Call.cancel() here
        }
    }
Kishan Vaghela
  • 7,678
  • 5
  • 42
  • 67