Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Practical RxJava for Android
Tomáš Kypta
@TomasKypta
What’s RxJava?
What’s RxJava?
• composable data flow
• push concept
• combination of
• observer pattern
• iterator pattern
• functional programming
RxJava…not sure how Android apps…
Typical non-reactive app
Event Source
Views Network DB …
Listener Listener Listener Listener
logic logiclogiclogic
State
Reactive app
Transformation
Event Source
Observable Observable Observable Observable
…
ObserverObserver
Views Network DB
RxJava data flow
Observable
.from(new String[]{"Hello", "Droidcon!"}) creation
RxJava data flow
Observable
.from(new String[]{"Hello", "Droidcon!"})
.map(new Func1<String, String>() {
@Override
public String call(String s) {
return s.toUpperCase(Locale.getDefault());
}
})
creation
RxJava data flow
Observable
.from(new String[]{"Hello", "Droidcon!"})
.map(new Func1<String, String>() {
@Override
public String call(String s) {
return s.toUpperCase(Locale.getDefault());
}
})
.reduce(new Func2<String, String, String>() {
@Override
public String call(String s, String s2) {
return s + ' ' + s2;
}
})
creation
transformation
RxJava data flow
Observable
.from(new String[]{"Hello", "Droidcon!"})
.map(new Func1<String, String>() {
@Override
public String call(String s) {
return s.toUpperCase(Locale.getDefault());
}
})
.reduce(new Func2<String, String, String>() {
@Override
public String call(String s, String s2) {
return s + ' ' + s2;
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Timber.i(s);
}
});
creation
transformation
subscription
Java 8 & Android
• use Retrolambda
• or jack compiler
• usable since build plugin 2.2.0
RxJava data flow
Observable
.from(new String[]{"Hello", "Droidcon!"})
.map(new Func1<String, String>() {
@Override
public String call(String s) {
return s.toUpperCase(Locale.getDefault());
}
})
.reduce(new Func2<String, String, String>() {
@Override
public String call(String s, String s2) {
return s + ' ' + s2;
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Timber.i(s);
}
});
creation
transformation
subscription
RxJava data flow with Java 8
creation
transformation
subscription
Observable
.from(new String[]{"Hello", "Droidcon!"})
.map(s -> s.toUpperCase(Locale.getDefault()))
.reduce((s,s2) -> s + ' ' + s2)
.subscribe(s -> Timber.i(s));
Key parts
• Observable
• Observer or Subscriber
• onNext(T)
• onCompleted()
• onError(Throwable)
• Subject
What is RxJava good for?
• making code simple and readable
• …with Java 8
• Async processing
• no AsyncTask, AsyncTaskLoader, …
Why…??
Async composition
• Async composition
• RxJava offers simple chaining of async operations
• eliminates callback hell
RxJava 1 vs. RxJava 2
RxJava 1 vs. RxJava 2
• RxJava 2 in RC now
• limited support in libraries
• they will coexist for some time
• different group ids
• io.reactivex vs. io.reactivex.rxjava2
• different package names
• rx vs. io.reactivex
• https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0
Operators
Operators
• work on Observable
• return an Observable
• can be chained
Common mistakes
• assuming mutability
• operators return new Observable
Observable<String> observable =
Observable.from(new String[]{"Hello", "Droidcon!"});
obserable.map(s -> s.toUpperCase(Locale.getDefault()));
obserable.reduce((s,s2) -> s + ' ' + s2);
Common Mistakes
•assuming mutability
•operators return new Observable
Observable<String> observable =
Observable.from(new String[]{"Hello", "Droidcon!"})
.map(s -> s.toUpperCase(Locale.getDefault()))
.reduce((s,s2) -> s + ' ' + s2);
Marble diagrams
• RxMarbles
Subscription
Subscription
Subscription s = mAppInfoProvider.getAppsObservable()
.subscribe(
appInfo -> Timber.i(appInfo.getPackageName()
);
Subscription
Subscription s = mAppInfoProvider.getAppsObservable()
.subscribe(
appInfo -> Timber.i(appInfo.getPackageName()
);
s.unsubscribe();
Subscription
Subscription s = mAppInfoProvider.getAppsObservable()
.subscribe(
appInfo -> Timber.i(appInfo.getPackageName()
);
s.unsubscribe();
Timber.i("unsubscribed: " + s.isUnsubscribed());
Subscription
Subscription s = mAppInfoProvider.getAppsObservable()
.subscribe(
appInfo -> Timber.i(appInfo.getPackageName()
);
s.unsubscribe();
Timber.i("unsubscribed: " + s.isUnsubscribed());
CompositeSubscription
CompositeSubscription compositeSubscription
= new CompositeSubscription();
CompositeSubscription
CompositeSubscription compositeSubscription
= new CompositeSubscription();
compositeSubscription.add(subscription);
CompositeSubscription
CompositeSubscription compositeSubscription
= new CompositeSubscription();
compositeSubscription.add(subscription);
compositeSubscription.unsubscribe();
CompositeSubscription
CompositeSubscription compositeSubscription
= new CompositeSubscription();
compositeSubscription.add(subscription);
compositeSubscription.unsubscribe();
compositeSubscription.clear();
CompositeSubscription
CompositeSubscription compositeSubscription
= new CompositeSubscription();
compositeSubscription.add(subscription);
compositeSubscription.unsubscribe();
compositeSubscription.clear();
Bridging non-Rx APIs
Observable creation
• create()
• better to avoid
private Object getData() {...}
public Observable<Object> getObservable() {
return Observable.create(subscriber -> {
subscriber.onNext(getData());
subscriber.onCompleted();
});
}
Observable creation
• just()
private Object getData() {...}
public Observable<Object> getObservable() {
return Observable.just(getData());
} !
just() & defer()
• defer()
private Object getData() {...}
public Observable<Object> getObservable() {
return Observable.defer(
() -> Observable.just(getData())
);
}
Observable creation
• fromCallable()
• callable invoked when an
observer subscribes
private Object getData() {…}
public Observable<Object> getObservable() {
return Observable.fromCallable(new Callable<Object>() {
@Override
public String call() throws Exception {
return getData();
}
});
}
Observable creation from async APIs
• fromEmitter()
Observable.<Event>fromEmitter(emitter -> {
Callback listener = new Callback() {
@Override
public void onSuccess(Event e) {
emitter.onNext(e);
if (e.isLast()) emitter.onCompleted();
}
@Override
public void onFailure(Exception e) {
emitter.onError(e);
}
};
AutoCloseable c = api.someAsyncMethod(listener);
emitter.setCancellation(c::close);
}, BackpressureMode.BUFFER);
Subject
Subject
• Observable & Observer
• bridge between non-Rx API
Subject
// consume
anObservable.subscribe(aSubject);
// produce
aSubject.subscribe(aSubscriber);
Subject
// bridging & multicasting
aSubject.subscribe(subscriber1);
aSubject.subscribe(subscriber2);
aSubject.onNext(item1);
aSubject.onNext(item2);
aSubject.onNext(item2);
aSubject.onCompleted()
Subject
• stateful
• terminal state
• don’t pass data after onComplete() or
onError()
Subject
• AsyncSubject
• BehaviorSubject
• ReplaySubject
• PublishSubject
• SerializedSubject
RxRelay
RxRelay
• safer cousin of Subject
• https://github.com/JakeWharton/RxRelay
RxRelay
• Relay = Subject - onComplete() - onError()
• Relay = Observable & Action1
• call() instead of onNext()
Subject vs. RxRelay
• Subject
• stateful
• Relay
• stateless
RxRelay
Relay relay = …
relay.subscribe(observer);
relay.call(A);
relay.call(B);
Threading
Threading
• Parts of a data flow can run on different threads!
• Threading in RxJava defined by Schedulers
Schedulers
• computation()
• io()
• newThread()
• from(Executor)
Android Schedulers
• mainThread()
• from(Looper)
Threading
• operators have their default Schedulers
• check Javadoc!
Threading
• just()
• current thread
• delay(long, TimeUnit)
• computation() thread
Threading
• subscribeOn(Scheduler)
• subscribes to Observable on the Scheduler
• observeOn(Scheduler)
• Observable emits on the Scheduler
subscribeOn()
• multiple calls useless
• only the first call works!
• for all operators
observeOn()
• can be called multiple times
• changes Scheduler downstream
Side effect methods
Side effect methods
• doOnNext()
• doOnError()
• doOnCompleted()
• doOnSubscribe()
• doOnUnsubscribe()
• …
Async composition
Async composition
apiEndpoint.login()
.doOnNext(accessToken ->
storeCredentials(accessToken))
Async composition
apiEndpoint.login()
.doOnNext(accessToken ->
storeCredentials(accessToken))
.flatMap(accessToken ->
serviceEndpoint.getUser())
Async composition
apiEndpoint.login()
.doOnNext(accessToken ->
storeCredentials(accessToken))
.flatMap(accessToken ->
serviceEndpoint.getUser())
.flatMap(user ->
serviceEndpoint.getUserContact(user.getId()))
Async composition
apiEndpoint.login()
.doOnNext(accessToken ->
storeCredentials(accessToken))
.flatMap(accessToken ->
serviceEndpoint.getUser())
.flatMap(user ->
serviceEndpoint.getUserContact(user.getId()))
Async composition
Android & RxJava
Android Lifecycle
• few complications
• continuing subscription during configuration
change
• memory leaks
Android Lifecycle
• continuing subscription during configuration
change
• replay()
• don’t use cache()
Android Lifecycle
• memory leaks
• bind to Activity/Fragment lifecycle
• use RxLifecycle
RxLifecycle
RxLifecycle
• auto unsubscribe based on Activity/Fragment
lifecycle
myObservable
.compose(
RxLifecycle.bindUntilEvent(
lifecycleObservable, ActivityEvent.DESTROY
)
)
.subscribe(…);
myObservable
.compose(RxLifecycle.bindActivity(lifecycleObs))
.subscribe(…);
RxLifecycle
• How to obtain ActivityEvent or
FragmentEvent?
A. rxlifecycle-components + subclass
RxActivity, RxFragment
B. Navi + rxlifecycle-navi
C. Write it yourself
RxLifecycle
public class MyActivity extends RxActivity {
@Override
public void onResume() {
super.onResume();
myObservable
.compose(bindToLifecycle())
.subscribe();
}
}
RxLifecycle & Navi
Navi
• https://github.com/trello/navi
• better listeners for Activity/Fragment events
• decoupling code from Activity/Fragment
naviComponent.addListener(Event.CREATE,
new Listener<Bundle>() {
@Override public void call(Bundle bundle) {
setContentView(R.layout.main);
}
}
);
• converting lifecycle callbacks into Observables!
RxNavi
RxNavi
.observe(naviComponent, Event.CREATE)
.subscribe(bundle -> setContentView(R.layout.main));
RxLifecycle & Navi
public class MyActivity extends NaviActivity {
private final ActivityLifecycleProvider provider
= NaviLifecycle.createActivityLifecycleProvider(this);
}
RxLifecycle & Navi
public class MyActivity extends NaviActivity {
private final ActivityLifecycleProvider provider
= NaviLifecycle.createActivityLifecycleProvider(this);
@Override
public void onResume() {
super.onResume();
myObservable
.compose(provider.bindToLifecycle())
.subscribe(…);
}
}
RxLifecycle & custom solution
public class MainActivityFragment extends Fragment {
BehaviorSubject<FragmentEvent> mLifecycleSubject =
BehaviorSubject.create();
}
RxLifecycle & custom solution
public class MainActivityFragment extends Fragment {
BehaviorSubject<FragmentEvent> mLifecycleSubject =
BehaviorSubject.create();
@Override public void onPause() {
super.onPause();
Timber.i("onPause");
mLifecycleSubject.onNext(FragmentEvent.PAUSE);
}
}
public class MainActivityFragment extends Fragment {
BehaviorSubject<FragmentEvent> mLifecycleSubject =
BehaviorSubject.create();
@Override public void onPause() {
super.onPause();
Timber.i("onPause");
mLifecycleSubject.onNext(FragmentEvent.PAUSE);
}
@Override public void onResume() {
super.onResume();
myObservable // e.g. UI events Observable
.compose(
RxLifecycle
.bindUntilEvent(mLifecycleSubject, FragmentEvent.PAUSE))
.doOnUnsubscribe(() -> Timber.i("onUnsubscribe"))
.subscribe(…);
}
}
RxLifecycle & custom solution
RxBinding
RxBinding
RxView.clicks(vBtnSearch)
.subscribe(
v -> {
Intent intent = new Intent(getActivity(),
SearchActivity.class);
startActivity(intent);
}
);
RxBinding
RxTextView.textChanges(vEtSearch)
.observeOn(Schedulers.io())
.flatMap(s -> mApiService.search(s.toString()))
.subscribe(
response -> Timber.i("Count: " + response.totalCount())
);
RxBinding
RxTextView.textChanges(vEtSearch)
.debounce(2, TimeUnit.SECONDS)
.observeOn(Schedulers.io())
.flatMap(s -> mApiService.search(s.toString()))
.subscribe(
response -> Timber.i("Count: " + response.totalCount())
);
RxBinding
RxTextView.textChanges(vEtSearch)
.debounce(2, TimeUnit.SECONDS)
.observeOn(Schedulers.io())
.flatMap(s -> mApiService.search(s.toString()))
.flatMap(response -> Observable.from(response.getItems())
.subscribe(
s -> Timber.i("item: " + s)
);
Retrofit
Retrofit
• sync or async API (Retrofit 2)
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);
@GET("group/{id}/users")
Observable<List<User>> groupList(@Path("id") int groupId);
• reactive API
Retrofit
• onNext() with Response, then onComplete()
• onError() in case of error
@GET("/data")
Observable<Response> getData(
@Body DataRequest dataRequest);
Retrofit
RxTextView.textChanges(vEtSearch)
.debounce(2, TimeUnit.SECONDS)
.observeOn(Schedulers.io())
.flatMap(s ->
mApiService.search(s.toString())
)
.flatMap(list -> Observable.from(list))
.subscribe(
s -> Timber.i("item: " + s)
);
Retrofit
RxTextView.textChanges(vEtSearch)
.debounce(2, TimeUnit.SECONDS)
.observeOn(Schedulers.io())
.flatMap(s ->
mApiService.search(s.toString())
)
.flatMap(list -> Observable.from(list))
.subscribe(
s -> Timber.i("item: " + s)
);
RxJava & SQLite
• use SQLBrite
• wrapper around SQLiteOpenHelper and
ContentResolver
SqlBrite sqlBrite = SqlBrite.create();
BriteDatabase db = sqlBrite
.wrapDatabaseHelper(openHelper, Schedulers.io());
Observable<Query> users = db
.createQuery("users", "SELECT * FROM users");
References
• https://github.com/ReactiveX/RxJava
• https://github.com/ReactiveX/RxAndroid
• https://github.com/JakeWharton/RxRelay
• https://github.com/trello/RxLifecycle
• https://github.com/trello/navi
• https://github.com/JakeWharton/RxBinding
• https://github.com/square/retrofit
• https://github.com/square/sqlbrite
• advanced RxJava blog https://akarnokd.blogspot.com
• http://slides.com/yaroslavheriatovych/frponandroid
Q&A

More Related Content

Practical RxJava for Android