Reactive Programming on Android with RxJava
Vincent Nien 2015/12/31
RX's power
滑鼠雙擊(多擊)設計

Imperative Programming
int x, y, z;
x = 1;
y = 2;
z = x + y; // 1 + 2
++y;
printf("%d", z); // z = 3
現在有三個箱子x, y, z
x裡面放著一顆球,y裡面放著兩顆球
z的箱子放著x跟y這兩個箱子
問: z的箱子裡面現在有幾顆球?
What is Reactive
現在你在y箱子中多放一顆球
問: z的箱子裡面現在有幾顆球?
實際範例
Observable Pattern?
A style of programming based on two key ideas: continuous time-varying behaviors, and event-based reactivity
Functional Reactive Programming

http://blog.csdn.net/smzhangyang/article/details/47006663

http://wiki.jikexueyuan.com/project/android-weekly/issue-145/introduction-to-RP.html
響應式編程 - 一種關注在非同步及資料流的編程方式
RX的特色
- 函數式 - 避免複雜狀態,使用簡單的input/output來處理資料流
- 簡潔 - Operators可以將複雜的邏輯變成簡單的代碼
- 錯誤處理 - 強大的錯誤處理機制
- 非同步處理 - 簡易的非同步執行序處理
Why Rx
"讓非同步的設計更加直覺" -- Sam Lee
"隨著程式邏輯變得複雜,依然能夠保持簡潔"-- 扔物線
"實現鏈式調用,邏輯清晰簡單" -- Coryphaei
Callback
FRP


http://www.infoq.com/cn/articles/functional-reactive-programming
RxJava
- Observable
- Transformation
Observable

Observable
Observer<T> observer = new Observer<T>() {
@Override
public final void onCompleted() {
}
@Override
public final void onError(Throwable e) {
}
@Override
public final void onNext(T args) {
}
};
observable.flip().subscribe(observer);
Creating Observable

Observable<String> ob = Observable.just("Something");
Creating Observable
List<String> aList = ...;
Observable<String> ob = Observable.from(aList);

Functional Language
object FunSets {
type Set = Int => Boolean
def contains(s: Set, elem: Int): Boolean = s(elem)
def singletonSet(elem: Int): Set = (x: Int) => elem == x
def union(s: Set, t: Set): Set = (x: Int) => contains(s, x) || contains(t, x)
def intersect(s: Set, t: Set): Set = (x: Int) => contains(s, x) && contains(t, x)
def diff(s: Set, t: Set): Set = (x: Int) => contains(s, x) && !contains(t, x)
def filter(s: Set, p: Int => Boolean): Set = (x: Int) => contains(s, x) && p(x)
val bound = 1000
def forall(s: Set, p: Int => Boolean): Boolean = {
def iter(a: Int): Boolean = {
if (a > bound) true
else if (contains(s, a)) filter(s, p)(a) && iter(a + 1)
else iter(a + 1)
}
iter(-bound)
}
Data Structure?

Observable
.range(0, 5)
.map(x -> toBinaryString(x*x))
.subscribe(s -> println(s),
err -> err.printStackTrace(),
() -> println("done"));
0
1
100
1001
10000
done

Observable
.range(1, 3)
.flatMap(x -> Observable.just(x).repeat(x))
.subscribe(System.out::println);
1
2
2
3
3
3

Observable
.range(0, 10)
.filter(x -> (x % 2) == 0)
.subscribe(System.out::println);
0
2
4
6
8
來舉些例子吧
不偷資料的WhosCall
簡易流程
- 有人打電話進來
- 從junkcall.org 查詢號碼資料
- 顯示到浮動視窗上


一般寫法
public static List<JunkCall> queryJunkCall(String number) {
List<JunkCall> list = new ArrayList<>();
try {
String content = getContent(URL.concat(number));
List<String> data = parse(content);
for(String desc : data) {
list.add(JunkCall.create(number, desc));
}
} catch(Exception e) {
}
return list;
}
使用RxJava
public static Observable<JunkCall> queryJunkCall(String number) {
return Observable.just(number)
.map(new Func1<String, String>() {
@Override
public String call(String s) {
return URL_JUNKCALL.concat(s);
}
})
.flatMap(new Func1<String, Observable<String>>() {
@Override
public Observable<String> call(String s) {
return getContent(s);
}
})
.flatMap(new Func1<String, Observable<String>>() {
@Override
public Observable<String> call(String s) {
return parse(s);
}
})
.map(new Func1<String, JunkCall>() {
@Override
public JunkCall call(String desc) {
return JunkCall.create(number, desc)
}
});
}
WTF...
With Lambda Expression / Method Reference
public static Observable<JunkCall> queryJunkCall(String number) {
return Observable.just(number)
.map(s -> URL_JUNKCALL.concat(s))
.flatMap(JunkCallParser::getContent)
.flatMap(JunkCallParser::parse)
.map(desc -> JunkCall.create(number, desc));
}
Comparison
public static Observable<JunkCall> queryJunkCall
(String number) {
return Observable
.just(number)
.map(s -> URL_JUNKCALL.concat(s))
.flatMap(JunkCallParser::getContent)
.flatMap(JunkCallParser::parse)
.map(desc -> JunkCall.create(number, desc));
}
public static List<JunkCall> queryJunkCall
(String number) {
List<JunkCall> list = new ArrayList<>();
try {
String content = getContent(URL.concat(number));
List<String> data = parse(content);
for(String desc : data) {
list.add(JunkCall.create(number, desc));
}
} catch(Exception e) {
}
return list;
}
擴充功能
Everything is stream
Add Retry
public static List<JunkCall> queryJunkCallWithRetry(String number) {
List<JunkCall> list = new ArrayList<>();
int retry = 0;
boolean success;
do {
try {
String content = getContent(URL.concat(number));
List<String> data = parse(content);
for(String desc : data) {
list.add(JunkCall.create(number, desc));
}
success = true;
} catch(Exception e) {
success = false;
}
} while(!success && retry++ < 1);
return list;
}
Add Filter
public static List<JunkCall> queryJunkCallWithRetry(String number) {
List<JunkCall> list = new ArrayList<>();
int retry = 0;
boolean success;
do {
try {
String content = getContent(URL.concat(number));
List<String> data = parse(content);
for(String desc : data) {
if(!TextUtils.isEmpty(desc)) {
list.add(JunkCall.create(number, desc));
}
}
success = true;
} catch(Exception e) {
success = false;
}
} while(!success && retry++ < 1);
return list;
}

http://www.slideshare.net/jingtw/in-in-der

http://wiki.jikexueyuan.com/project/android-weekly/issue-145/introduction-to-RP.html
Add Retry/Filter with RxJava
public static Observable<JunkCall> queryPhoneNumber(String number) {
return Observable
.just(number)
.map(s -> URL_JUNKCALL.concat(s))
.flatMap(JunkCallParser::getContent)
.flatMap(JunkCallParser::parse)
.filter(s -> !TextUtils.isEmpty(s))
.map(desc -> JunkCall.create(number, desc))
.retry(1);
}

http://www.slideshare.net/jingtw/in-in-der
Comparison
public static Observable<JunkCall>
queryJunkCall(String number) {
return Observable
.just(number)
.map(s -> URL_JUNKCALL.concat(s))
.flatMap(JunkCallParser::getContent)
.flatMap(JunkCallParser::parse)
.filter(s -> !TextUtils.isEmpty(s))
.map(desc -> JunkCall.create(number, desc))
.retry(1);
}
public static List<JunkCall>
queryJunkCallWithRetry(String number) {
List<JunkCall> list = new ArrayList<>();
int retry = 0;
boolean success;
do {
try {
String content = getContent(URL.concat(number));
List<String> data = parse(content);
for(String desc : data) {
if(!TextUtils.isEmpty(desc)) {
list.add(JunkCall.create(number, desc));
}
}
success = true;
} catch(Exception e) {
success = false;
}
} while(!success && retry++ < 1);
return list;
}

http://www.infoq.com/cn/articles/functional-reactive-programming

public static Observable<JunkCall>
queryJunkCall(String number) {
return Observable
.just(number)
.map(s -> URL_JUNKCALL.concat(s))
.flatMap(JunkCallParser::getContent)
.flatMap(JunkCallParser::parse)
.filter(s -> !TextUtils.isEmpty(s))
.map(desc -> JunkCall.create(number, desc))
.retry(1);
}
將查詢結果顯示到UI
實現非同步串接stream
一般寫法
new Thread(new Runnable() {
public void run() {
List<JunkCall> data = JunkCallParser.queryJunkCall(number);
final List<String> descriptions = new ArrayList<>();
// distinct
Set<String> set = new HashSet<>();
for(JunkCall call : data) {
set.add(call.description());
}
for(String s : set) {
descriptions.add(s);
}
runOnUiThread(new Runnable() {
public void run() {
mWindow.setResult(JunkCallService.this,
descrptions, number);
}
});
}
}).start();
With RxJava
JunkCallParser
.queryJunkCall(number)
.distinct(JunkCall::description)
.map(JunkCall::description)
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(junkCalls -> {
mWindow.setResult(JunkCallService.this,
junkCalls, number);
}
});
Comparison
JunkCallParser
.queryJunkCall(number)
.distinct(JunkCall::description)
.map(JunkCall::description)
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(junkCalls ->
mWindow.setResult(JunkCallService.this,
junkCalls, number);
);
new Thread(new Runnable() {
public void run() {
List<JunkCall> data =
JunkCallParser.queryJunkCall(number);
final List<String> descriptions =
new ArrayList<>();
// distinct
Set<String> set = new HashSet<>();
for(JunkCall call : data) {
set.add(call.description());
}
for(String s : set) {
descriptions.add(s);
}
runOnUiThread(new Runnable() {
public void run() {
mWindow.setResult(JunkCallService.this,
descrptions, number);
}
});
}
}).start();

Result
Say Hello to Callback Hell
/* API */
void getFromServer(String key, Action1<String> callback);
void getFromDB(String key, Action1<String> callback);
/* Code */
btnClick.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
getFromDB("myid", new Action1<String>() {
public void call(String s) {
getFromServer(s, new Action1<String>() {
public void call(final String s) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(context, s, LENGTH_LONG).show();
}
});
/* ... a lot of }) ... */
http://misgod.github.io/Slide-FRP-Android/
Say Goodbye to Callback Hell
/* API */
Observable<String> getFromServer(String key);
Observable<String> getFromDB(String key);
/* Code */
ViewObservable
.clicks(btnClick)
.map(x -> "myid")
.observeOn(Schedulers.io())
.flatMap(this::getFromDB)
.flatMap(this::getFromServer)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(x -> Toast.makeText(context, x, LENGTH_LONG).show());
Use rx.Observable
http://misgod.github.io/Slide-FRP-Android/
All In One search
Observable<SearchResult> g = googleSearch.search(keyword).retry(3);
Observable<SearchResult> b = bingSearch.search(keyword).retry(3);
Observable<SearchResult> y = yahooSearch.search(keyword).retry(3);
Observable
.merge(g, b, y)
.distinct(site -> site.url)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
site -> appendDataForUI(),
err -> errorhandle(err));
http://misgod.github.io/Slide-FRP-Android/
Requirements
- A search engine includes google/yahoo/bing search results.
- Should search different engines in parallel
- Retry 3 times when search fail
- remove redundant url
A simple EventBus
PublishSubject<Object> subject = PublishSubject.create(); //Global Singleton
http://misgod.github.io/Slide-FRP-Android/
//...In Class A...
subject.filter(x -> x instanceof DataUpdateAction)
.subscribe( x -> ... doSomething ...);
//...In Class B...
subject.filter(x -> x instanceof DeleteAction)
.subscribe( x -> ... doSomething ...);
//...In Class C...
subject.filter(x -> x instanceof RefreshAction)
.subscribe( x -> ... doSomething ...);
subject.onNext(aDataUpdateAction);
subject.onNext(aDataUpdateAction);
subject.onNext(aRefreshAction);
Conclusion
- 全世界都在往Functional Language發展
- Everything is a stream
- Try it in your next project
Q & A
Reactive Programming on Android with RxJava
By Vincent SH
Reactive Programming on Android with RxJava
- 1,396