rxloader

Additional

Language
Java
Version
N/A
Created
Feb 17, 2014
Updated
Apr 1, 2015 (Retired)
Owner
Evan Tatarka (evant)
Contributors
Evan Tatarka (evant)
2bard
eugecm
3
Activity
Badge
Generate
Download
Source code

Commercial

rxloader

Asynchronous operations in Android are very hard to get right due to the Activity lifecycle. AsyncTasks don't handle any of it making them difficult to use. Loaders handle many things for you, but have a clunky api and fall down anytime you want to do anything more complex than loading data immediately when the Activity or Fragment is shown.

This library builds upon rxjava to handle all these things for you with an easy-to-use api.

Install

Gradle

repositories {
  mavenCentral()
}

dependencies {
  compile 'me.tatarka.rxloader:rxloader:1.1.0'
}

Maven

<dependency>
  <groupId>me.tatarka.rxloader</groupId>
  <artifactId>rxloader</artifactId>
  <version>1.1.0</version>
</dependency>

Usage

The sample app shows some common use cases of handling observables.

Here is a simple example of loading data as soon as your activity starts.

public class MyActivity extends Activity {
  private RxLoaderManager loaderManager;

  public void onCreate(Bundle savedState) {
    // If you are using the support library, 
    // use RxLoaderManagerCompat.get(this) instead.
    loaderManager = RxLoaderManager.get(this);

    loaderManager.create(
      asyncThatReturnsObservable(),
      new RxLoaderObserver<Result>() {
        @Override
        public void onStarted() {
          // Show your progress indicator.
        }

        @Override
        public void onNext(Result result) {
          // Hide your progress indicator and show the result.
        }

        @Override
        public void onError(Throwable error) {
          // Hide your progress indicator and show that there was an error.
        }
      }
    ).start(); // Make sure you call this to kick things off.
  }
}

Or in a fragment

public class MyFragment extends Fragment {
  private RxLoaderManager loaderManager;

  public void onViewCreated(View view, Bundle savedInstanceState) {
    // If you are using the support library, 
    // use RxLoaderManagerCompat.get(this) instead.
    loaderManager = RxLoaderManager.get(this);

    loaderManager.create(
      asyncThatReturnsObservable(),
      new RxLoaderObserver<Result>() {
        @Override
        public void onStarted() {
          // Show your progress indicator.
        }

        @Override
        public void onNext(Result result) {
          // Hide your progress indicator and show the result.
        }

        @Override
        public void onError(Throwable error) {
          // Hide your progress indicator and show that there was an error.
        }
      }
    ).start(); // Make sure you call this to kick things off.
  }
}

All observer callbacks run on the UI thread. If the Activity or Fragment is destroyed, the callbacks will not be called. If there is a configuration change, the relevant callbacks will be called again.

Here is an example of loading and reloading on a button press. Try doing this with loaders!

public class MyActivity extends Activity {
  private RxLoaderManager loaderManager;

  public void onCreate(Bundle savedState) {
    loaderManager = RxLoaderManager.get(this);

    final RxLoader<Result> myLoader = loaderManager.create(
      asyncThatReturnsObservable(),
      new RxLoaderObserver<Result>() {
        @Override
        public void onStarted() {
          // Show your progress indicator.
        }

        @Override
        public void onNext(Result result) {
          // Hide your progress indicator and show the result.
        }

        @Override
        public void onError(Throwable error) {
          // Hide your progress indicator and show that there was an error.
        }
      }
    );

    findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View button) {
        myLoader.restart();
      }
    });
  }
}

Note that the loader is still created in onCreate() and not on the button callback. This is necessary to handle configuration changes properly if the button was pressed first.

Passing arguments

If you want to pass arguments to your observable, you can use one of the overloads that takes a Func1<Arg, Observable<T>> or `Func2<Arg1, Arg2, Observable>``.

final RxLoader1<String, String> inputLoader = loaderManager.create(
  new Func1<String, Observable<String>>() {
    @Override
    public Observable<String> call(final String input) {
      return asyncMethod(input);
    }
  },
  new RxLoaderObserver<String>() {
    @Override
    public void onStarted() {
      // Show your progress indicator.
    }

    @Override
    public void onNext(String message) {
      // Hide your progress indicator and show the result.
    }
  }
);

buttonInput.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    inputLoader.restart(editInput.getText().toString());
  }
});

Tags

It is possible that you have multiple loaders for a given RxLoaderManager. In that case you must pass each one a unique tag (loaderManager.create(MY_TAG, observable, callback)). This is a string that identifies which callback is attached to which observable so it can be reattached on a configuration change.

You may notice the above examples do not have a tag. If none is provided, then then RxLoaderManager#DEFAULT is used.

Saving state

The Android OS might destroy and recreate your Activity sometimes. If you don't want to re-request data for your UI, you can save the result in the Activity's instance state.

This can be as easy as

loaderManager.create(observable, callback).save().start();

This assumes that the observable's value implements Parceable. If it doesn't, you can handle saving and restoring it yourself by passing in a SaveCallback.

loaderManager.create(observable, callback)
  .save(new SaveCallback<Result>() {
    @Override
    public void onSave(String tag, Result value, Bundle outBundle) {
      // Save the value in the bundle.
    }

    @Override
    public Result onRestore(String tag, Bundle savedState) {
      // Return the value from the bundle.
    }
  }).start();

Transient State

It may be the case that the result returned by your observable is transient and you don't want it to show any more after it's been handled (a Toast for example). In that case, you can call clear() on the loader to reset it so that it will no longer be delivered on configuration changes.

final RxLoader<Result> loader = loaderManager.create(
  asyncThatReturnsObservable(),
  new RxLoaderObserver<Result>() {
    @Override
    public void onStarted() {
      // Show your progress indicator.
    }

    @Override
    public void onNext(Result result) {
      // Hide your progress indicator and show the result.
    }

    @Override
    public void onError(Throwable error) {
        Toast.makeText(context, error.getMessage(), Toast.LENGTH_SHORT).show();
        loader.clear(); // onError() won't get called again when you rotate. 
    }
  }
).start(); // Make sure you call this to kick things off.

A note about usage

RxLoader does nothing to effect to thread in which the observable you passed in is run on. That means if you use Observable.create(...) you may find that your "background" action is run on the UI thread. Fixing this is easy, just use observable.subscribeOn(...) to run the observable on the given scheduler.