-->

Android manage multi request rxJava on rotation de

2019-06-19 01:55发布

问题:

I'm using MVVM on android application and i want to manage requests and rxJava on device rotation, how can i disable request after rotation device and countinue from last request?

this is my simple code to know how can i doing that, but i can't find any document and sample code about it

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    binding = DataBindingUtil.setContentView(this, R.layout.activity_register);
    ...
    Observer<String> myObserver = new Observer<String>() {
        @Override
        public void onError(Throwable e) {
            // Called when the observable encounters an error
        }

        @Override
        public void onComplete() {

        }

        @Override
        public void onSubscribe(Disposable d) {

        }

        @Override
        public void onNext(String s) {
            // Called each time the observable emits data
            Log.e("MY OBSERVER", s);
        }
    };

    Observable.just("Hello").subscribe(myObserver);
}

I'm using latest version of rxJava

回答1:

Handling rotation is a cool challenge in Android. There're a few ways to do that.

1- Services: You can use a service and handle your network requests or other background operations in service. Also with Services, you'll seperate your business logic from ui.

2- Worker Fragment: Worker fragment is a fragment instance without a layout. You should set your worker fragment's retainInstanceState to true. So you'll save your fragment from orientation change and will not lose your background operations.

Why Worker Fragment? If you set retainInstanceState true to a fragment with layout, you'll leak views.

If you're using MVVM you can implement ViewModel as a Worker Fragment which as setRetainInstanceState = true

3- Global Singleton Data Source: You can create a global singleton data source class which handles your operations in an independent scope from Activity / Fragment lifecycle in your application.

4- Loaders: Loaders can recover state from orientation changes. You handle your operations with loaders but they are designed to load data from disk and are not well suited for long-running network requests.

Extra: You can use Path's Priority Job Queue to persist your jobs: https://github.com/path/android-priority-jobqueue

Edit: You can check my repo for handling device rotation without using Google's new architecture components. (As an example of Worker Fragment which i pointed in my answer.) https://github.com/savepopulation/bulk-action



回答2:

You have the following options:

  • Use some global Singleton, or your Application class, that holds your logic, not within your Activity's lifecycle
  • Use a Service that runs next to your activity/application
  • Use a Loader

Global state is often bad and makes your code hard to test / debug. Services tend to be overkill.

For your use case of device rotation and continuing where one left off you'd usually use a Loader, which keeps running on rotation and only gets destroyed once you leave the activity.

I also recently wrote an article about one possible solution to use Loaders together with RxJava to keep state during orientation changes.



回答3:

You can take advantage of Fragment#setRetainInstance(true). With that flag set, fragment is not destroyed after device rotation and can be used as an object container. Please look at this sample which also stores Observable - https://github.com/krpiotrek/RetainFragmentSample



回答4:

you need to override

@Override
public void onSaveInstanceState(Bundle outState) {
  super.onSaveInstanceState(outState);
}

When device is rotated store data in bundle then inside on create check

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if(savedInstanceState == null){
   //saved instance is null
}else{
    //get your stored values here
    counter = savedInstanceState.getInt("value",0); //here zero is the default value
 }
}


回答5:

How I'm doing this is to have a singleton class (or any long living Object as explained by savepopulation earlier, but - the trick is to store the loaded data in a BehaviorSubject, and subscribe to that subject in the Activity instead of the original network request.

This way:

public class MyNetworkSingleton {
  // This static service survives orientation changes
  public static MyNetworkSingleton INSTANCE = new MyNetworkSingleton();

  private final BehaviorSubject<String> dataSubject = BehaviorSubject.create();

  public Observable<String> getData() {
     if (!dataSubject.hasValue()) {
        refreshData(); // No data is loaded yet, load initial data from network
     }

     return dataSubject;
  }

  public void refreshData() {
     someDataSourceCall().subscribe(new Observer<String>() {
       @Override
       public void onError(Throwable e) {
           // Remember, this point also needs error handling of some form,
           // e.g. propagating the error to the UI as a Toast
       }

       @Override
       public void onComplete() {

       }

       @Override
       public void onSubscribe(Disposable d) {

       }

       @Override
       public void onNext(String data) {
          dataSubject.onNext(data); // this refreshes the internally stored data
       }
     });
  }

  private Observable<String> someDataSourceCall() {
     return // some network request here etc. where you get your data from
  }
}

and then:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    Observer<String> myObserver = new Observer<String>() {
        @Override
        public void onError(Throwable e) {
            // Called when the observable encounters an error
        }

        @Override
        public void onComplete() {

        }

        @Override
        public void onSubscribe(Disposable d) {

        }

        @Override
        public void onNext(String s) {
            // Called each time the observable emits data
            Log.e("MY OBSERVER", s);
        }
    };

    MyNetworkSingleton.INSTANCE.getData().subscribe(myObserver);

    myRefreshButton.setOnClickListener(new Button.OnClickListener() {
        public void onClick(View v) {
            // refresh data from network only when button is pressed
            MyNetworkSingleton.INSTANCE.refreshData();
        }
    });
}

This way only first time you need the data from network it will be loaded, or when the user clicks a refresh button (myRefreshButton).