Giter Site home page Giter Site logo

rxloader's Introduction

[Deprecated]

There are better solutions out there, would recommend using LiveData or AutoDispose with rx.

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 {
  implementation '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<T>>.

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.

rxloader's People

Contributors

2bard avatar eugecm avatar evant avatar hendrawd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rxloader's Issues

Clarify cleanup strategy

I have read the README and looked at code a bit, but still don't get, how to perform basic cleanup (e.g. onLoaderReset) using your library. Could you explain that, please?

Example download file

Hi!
Can you add the example of downloading files from the Internet using your library and ProgressBar?
Thank you!

Usage in DialogFragment

I am trying to use the rxloader inside of a DialogFragment. Although .start() is invoked after a config change the RxLoaderObserver is not called. This behavior appears when the screen is rotated after the RxLoaderObserver has successfully popuplatet the view.

Async subscription

Hi, it seems like rxloader subscribes on the current thread. Wouldnt it simplify the creation of an observable which does its work in the background to subscribe on a worker/background thread like AsyncTask?

DialogFragment gives recursive usage exception

Here's a basic LoginFragment extends DialogFragment:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        rxLoaderManager = RxLoaderManagerCompat.get(this);
    }

and then in host activity:

new LoginFragment().show(getSupportFragmentManager(), null);

gives

 java.lang.IllegalStateException: Recursive entry to executePendingTransactions
            at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1461)
            at android.support.v4.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:1953)
            at android.support.v4.app.Fragment.performDestroy(Fragment.java:2003)
            at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1076)
            at android.support.v4.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1223)
            at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:709)
            at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1489)
            at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:454)
            at android.os.Handler.handleCallback(Handler.java:725)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5283)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
            at dalvik.system.NativeStart.main(Native Method)

Note: I tried avoid support package – the same result.
//
Another problem: giving it a tag like this

new LoginFragment().show(getSupportFragmentManager(), "login_tag");

throws:

java.lang.IllegalStateException: Can't change tag of fragment LoginFragment{424e6cb0 #2 tag}: was login_tag now me.tatarka.rxloader.RxLoaderManager_fragment
            at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:420)
            at android.support.v4.app.BackStackRecord.add(BackStackRecord.java:401)
            at me.tatarka.rxloader.RxLoaderManagerCompat.get(RxLoaderManagerCompat.java:46)
            at com.bridgecrm.ui.fragment.LoginFragment.onCreate(LoginFragment.java:72)
            at android.support.v4.app.Fragment.performCreate(Fragment.java:1763)
            at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:913)
            at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1126)
            at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:739)
            at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1489)
            at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:454)
            at android.os.Handler.handleCallback(Handler.java:725)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5283)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
            at dalvik.system.NativeStart.main(Native Method)

Example won't run due to gradle issue

I've cloned the example on my pc but i'm unable to run it due to a couple of gradle errors. specifically
1."Error:(21, 14) Failed to resolve: android:android:4.3_r2"
2. "Error:(22, 14) Failed to resolve: android.support:compatibility-v4:21.0.0"

For error one i couldn't find android 4.3_r2 listed in the sdk although i have all of them installed till ver 24.2
For error 2, being a newbie , i've no clue what the issue is.
Thanks.

Creating loader in onCreate doesn't work for fragments

In readme it is suggested to setup loader in onCreate. As I can see it will only work if you have all your views setup before creating loader, otherwise it can call onNext and view is still null.

So there is an issue with fragments, since none of the views is available in onCreate, only in onViewCreated.

Or maybe it is ok to create loader in onActivityCreated?

Problems with DrWeb

When I compile my project with 'me.tatarka.rxloader:rxloader:1.1.0' my antivirus delete jar file with warning: Android.SmsSpy.410.origin.
screenshot_1

RxloaderManager doesn't work for fragments

Looks like there is a bug in
me.tatarka.rxloader.RxLoaderManagerCompat#get(android.support.v4.app.Fragment)

  1. it adds calling fragment to activity again instead of adding new RxLoaderBackendFragmentCompat, this way you will get stackoverflow.
  2. android doesn't support nested retained fragments

onNext() get called during RxLoader creation

I have a fragment hosted by an activity. Within the fragment, I create an RxLoader by:

RxLoader1<DealSavable, MyData> myLoader;

myLoader = RxLoaderManager.get(getActivity()).create(..., new RxLoaderObserver() {
@OverRide
public void onNext(MyData value) {
}
...
});

myLoader.restart();

The problem is: If the loader retrieved some data, and after that, if the activity is re-created, during the above "create()" method call, the onNext() is called immediately with previously loaded stale data.

Is this a bug? How can I make sure that onNext() is called only when "restart()" is called?

onNext() not get called

Note sure whether this is a bug, but I would like to post it here:

I have an activity that uses RxLoader to fetch data from server upon activity creation, and populates a list view. I also have test code that tries to click the first row of the list view after it's populated.

Because I am using RxLoader, I have to write a customized IdlingResource for Espresso. However, the problem is that the test finishes (fails) before my listview is populated.

Here is my Activity code:

    RxLoaderManager.get(this).create(
        "my_loader",
        mRestService.loadData(),
        new RxLoaderObserver<MyData>() {
            @Override
            public void onNext(MyData data) {
                // populate listview with data here
                ...
            }

});

Here is my IdlingResource implementation:

public class IdlingApiServiceWrapper implements IdlingResource {

public Observable loadData(){
counter.incrementAndGet();
Observable observable = api.loadData().finallyDo(new Action0() {
@OverRide
public void call() {
counter.decrementAndGet();
notifyIdle();
}
});
return observable;
} }

Problem is that: When I run the test code, the method "counter.decrementAndGet()" is always called before the test finishes, and the "onNext()" method in my Activity did not get called.

Is this a bug with rxloader?

Error on gradle

I'm trying to add rxloader to my project, but I'm getting the following error:

Warning:Module 'me.tatarka.rxloader:rxloader:1.1.0' depends on one or more Android Libraries but is a jar

How do I fix that?

Create loader not in onCreate() method

Documentation for all RxLoaderManager#create methods contains requirement:

This should be called in Activity.onCreate(android.os.Bundle) or similar.

Is it totally impossible to create loader not in onCreate method (for example, create loader to upload photos selected by user) and make this loader persistable across activity configuration changes?

onStarted called twice.

Hi,
first thx for this great library which i would like to use but i am wondering why the onStartedMethod is called twice after starting a RxLoader. The following code is based on the sample application and called in the onCreate method of an activity:

RxLoaderManager.get(this).create(delay(),
    new RxLoaderObserver<String>() {
        @Override
        public void onStarted() {
            System.out.println("Starting");
            // Called twice, why?
        }

        @Override
        public void onNext(String value) { 
            // Called onec, correct
        }

        @Override
        public void onError(Throwable error) { }
    }
).start();

public static Observable<String> delay() {
    return Observable.timer(2, TimeUnit.SECONDS).map(new Func1<Long, String>() {
        @Override
        public String call(Long aLong) {
            return "Async Complete!";
        }
    });
}

Passing arguments

Not sure if it a good place to ask.

How would you pass arguments to a loader?
Let's say I have a search function, so on each restart I need to pass new arguments to observable.
I there any good way to do it?

RuntimeException in onDestroy

This is from RxLoaderBackendNestedFragmentCompat:

@Override
public void onDestroy() {
    super.onDestroy();
    if (!hasSavedState) {
        RxLoaderBackendFragmentHelper helper = getHelper();
        if (helper != null) {
            helper.onDestroy(getStateId());
        }
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    hasSavedState = true;
    RxLoaderBackendFragmentHelper helper = getHelper();
    if (helper != null) {
        helper.onSaveInstanceState(outState);
    }
}

If I rotate the device, the 'onSaveInstanceState' method is invoked, and the 'hasSavedState' is set to true, and in the onDestroy, the check is ok. Nothing wrong here.

But when I press back to leave the fragment and the Activity. The 'onSaveInstanceState' is not invoked and the 'onDestroy' crashes the app:

java.lang.RuntimeException: Unable to destroy activity {my.app/my.app.activities.MyActivity}: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3831)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3849)
at android.app.ActivityThread.-wrap5(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1398)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1533)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1551)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:696)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:662)
at me.tatarka.rxloader.RxLoaderBackendNestedFragmentCompat.getHelper(RxLoaderBackendNestedFragmentCompat.java:45)
at me.tatarka.rxloader.RxLoaderBackendNestedFragmentCompat.onDestroy(RxLoaderBackendNestedFragmentCompat.java:76)
at android.support.v4.app.Fragment.performDestroy(Fragment.java:2322)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1240)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1290)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1272)
at android.support.v4.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:2186)
at android.support.v4.app.Fragment.performDestroy(Fragment.java:2318)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1240)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1290)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1272)
at android.support.v4.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:2186)
at android.support.v4.app.FragmentController.dispatchDestroy(FragmentController.java:271)
at android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:388)
at android.support.v7.app.AppCompatActivity.onDestroy(AppCompatActivity.java:209)
at my.app.activities.BaseActivity.onDestroy(BaseActivity.java:73)
at android.app.Activity.performDestroy(Activity.java:6407)
at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1142)
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3818)
... 9 more

What am I doing wrong?

AAR packaging

I noticed the rxloader library uses java plugin; it should be an Android-specific library. Should it be packaged under AAR?

onStarted() gets called after configuration change when the loader has been cleared

I saw this was discussed a bit in #15, but I figured I'd open a new issue since it's a separate bug (assuming I understand how it's supposed to work).

Situation:

  1. Call RxLoader.clear(), for example in onCompleted().
  2. After an orientation change, onStarted() gets called (but no onNext() or onCompleted() because I've cleared the loader).

Issue:
I don't think onStarted() should get called on orientation change if the loader has been cleared.

To give more info on my use case, I'm storing the data separately in a retained fragment to handle orientation change, and only using the RxLoader to get the data initially, or to re-load the data when switching data sets. The reason I'm managing the data separately and not just using RxLoader for orientation changes is because the data can be manipulated by the UI, so the data emitted by the RxLoader after the orientation change is stale, and I don't want to have to re-fetch it from my database (especially since I already have the modified data).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.