SlideShare a Scribd company logo
1 of 33
다시 보는 RxJava
네이버 노재춘
Rx Contract #1-1
no more messages should arrive after an onError or
onComplete message
RxView.clicks(button)
.flatMap(ignored -> getBestSeller()
.subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bookTitle -> title.setText(bookTitle),
e -> Toast.makeText(this, "문제 발생",
Toast.LENGTH_LONG).show()
);
무한 이벤트에서 문제 발생 가능
Rx Contract #1-2
RxView.clicks(button)
.flatMap(ignored -> getBestSeller()
.subscribeOn(Schedulers.io())
.map(title -> new ViewState.Result(title)) // (1)
.cast(ViewState.class) // (2)
.onErrorReturn(e -> new ViewState.Error(e))) // (3)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(viewState -> {
if (viewState instanceof ViewState.Result) { // (4)
title.setText(((ViewState.Result) viewState).title);
} else if (viewState instanceof ViewState.Error) { // (5)
Toast.makeText(this, "문제 발생", Toast.LENGTH_LONG).show();
}
});
Rx Contract #1-3
interface ViewState {
class Error implements ViewState {
Throwable e;
Error(Throwable e) {
this.e = e;
}
}
class Result implements ViewState {
String title;
Result(String title) {
this.title = title;
}
}
}
http://hannesdorfmann.com/android/
mosby3-mvi-1
visual bugs such as displaying a loading
indicator (“loading state”) and error indicator
(“error state”) at the same time => exactly
one output
Rx Contract #2-1
스트림에 널(null) 허용 안됨
@Test(expected = NullPointerException.class)
public void nullEvent() {
Observable.just("Hello", null, "RxJava")
.subscribe(System.out::println,
System.err::println);
}
Observable 생성
에서 에러 발생
Rx Contract #2-2
@Test
public void nullEventOnCreate() {
Observable.create(emitter -> {
emitter.onNext("Hello");
emitter.onNext(null);
emitter.onNext("RxJava");
}).subscribe(System.out::println,
System.err::println);
}
java.lang.NullPointerException:
onNext called with null. Null
values are generally not
allowed in 2.x operators and
sources.
Rx Contract #2-3
@Test
public void nullEventPossible() {
Observable.just(1, 2, -1, 1, 2)
.map(dollar -> getCurrentPrice(dollar))
.subscribe(System.out::println,
System.err::println);
}
private String getCurrentPrice(int dollar) {
if (dollar < 0) {
return null;
}
return (dollar * 1000) + " won";
}
1000 won
2000 won
java.lang.NullPointerException:
The mapper function returned
a null value.
Rx Contract #2-4
@Test
public void nullEventPossible3() {
Observable.just(1, 2, -1, 1, 2)
.map(dollar -> getCurrentPrice3(dollar))
.onErrorReturnItem("0 won")
.subscribe(System.out::println,
System.err::println,
() -> System.out.println("onComplete"));
}
private String getCurrentPrice3(int dollar) {
if (dollar < 0) {
throw new IllegalArgumentException("dollar should be bigger than 0");
}
return (dollar * 1000) + " won";
} [1000 won, 2000 won, 0 won, onComplete]
Rx Contract #2-5
@Test
public void nullEventPossible4() {
Observable.just(1, 2, -1, 1, 2)
.flatMap(dollar -> getCurrentPrice4(dollar)
.onErrorReturnItem("0 won")) // (1)
.subscribe(System.out::println,
System.err::println);
}
private Observable getCurrentPrice4(int dollar) {
if (dollar < 0) {
return Observable.error(
new IllegalArgumentException("dollar should be bigger than 0")); // (2)
}
return Observable.just((dollar * 1000) + " won"); // (3)
}
Rx Contract #3-1
Assume observer instances are called in a serialized fashion
Observable obs = Observable.create(emitter -> {
new Thread(() -> {
emitter.onNext(1);
emitter.onNext(3);
. . . .
emitter.onNext(9);
// emitter.onComplete(); // (1)
}).start();
new Thread(() -> {
emitter.onNext(2);
emitter.onNext(4);
. . . .
emitter.onNext(10);
// emitter.onComplete(); // (2)
}).start();
});
onComplete 넣
을 수 없음
Rx Contract #3-2
obs.subscribe(value -> System.out.println(
Thread.currentThread().getName() + ":" + value),
System.err::println,
() -> System.out.println("onComplete"));
onComplete 불
리지 않음
Rx Contract #3-3
Observable obs1 = Observable.create(emitter -> {
new Thread(() -> {
emitter.onNext(1);
. . . .
emitter.onNext(9);
emitter.onComplete(); // (1)
}).start();
});
Observable obs2 = Observable.create(emitter -> {
new Thread(() -> {
emitter.onNext(2);
. . . .
emitter.onNext(10);
emitter.onComplete(); // (2)
}).start();
});
(1), (2)
모두 필요
Rx Contract #3-4
Observable.merge(obs1, obs2).subscribe(value ->
System.out.println(Thread.currentThread().getName() + ":" + value),
System.err::println,
() -> System.out.println("onComplete")
);
onComplete 정
상 호출
Rx Contract #3-5
Observable obs1 = Observable.create(emitter -> {
emitter.onNext(1);
emitter.onNext(3);
. . . .
emitter.onNext(9);
emitter.onComplete();
}).subscribeOn(Schedulers.computation());
Observable obs2 = Observable.create(emitter -> {
emitter.onNext(2);
emitter.onNext(4);
. . . .
emitter.onNext(10);
emitter.onComplete();
}).subscribeOn(Schedulers.computation());
Rx Contract #3-6
Observable.merge(obs1, obs2).subscribe(value ->
System.out.println(
Thread.currentThread().getName() + ":" + value),
System.err::println,
() -> System.out.println("onComplete")
);
flatMap thread #1-1
repository.getBookCategories().toObservable()
.flatMapIterable(categories -> categories)
.flatMapSingle(category -> repository.getCategoryBooks(category.categoryId))
.subscribeOn(Schedulers.io())
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()
repository.getBookCategories().toObservable()
.subscribeOn(Schedulers.io())
.flatMapIterable(categories -> categories)
.flatMapSingle(category ->
repository.getCategoryBooks(category.categoryId)
.subscribeOn(Schedulers.io()))
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()));
flatMap thread #1-2
flatMap thread #2 단일 이벤트 연결
repository.getRegion(location)
.map(region -> region.areaCode)
.flatMap(repository::getWeather)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(weather -> title.setText(weather.toString()));
repository.getRegion(location)
.subscribeOn(Schedulers.io())
.map(region -> region.areaCode)
.flatMap(areaCode -> repository.getWeather(areaCode)
.subscribeOn(Schedulers.io()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(weather -> title.setText(weather.toString()));
merge thread #1-1
Single.merge(repository.getBestSeller(),
repository.getRecommendBooks(),
repository.getCategoryBooks(7))
.subscribeOn(Schedulers.io())
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()));
Single.merge(repository.getBestSeller().subscribeOn(Schedulers.io()),
repository.getRecommendBooks().subscribeOn(Schedulers.io()),
repository.getCategoryBooks(7).subscribeOn(Schedulers.io()))
.collect(ArrayList::new, ArrayList::addAll)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> title.setText(books.toString()));
merge thread #1-2
zip thread #1-1
Single.zip(repository.getWeather(regionCode),
repository.getWeatherDetail(regionCode),
Pair::new)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(pair -> title.setText(pair.first + ", " + pair.second));
Single.zip(repository.getWeather(regionCode).subscribeOn(Schedulers.io()),
repository.getWeatherDetail(regionCode).subscribeOn(Schedulers.io()),
Pair::new)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(pair -> title.setText(pair.first + ", " + pair.second));
zip thread #1-2
RetryWhen #1
- SocketTimeout에 재시도(3번 재시도)
- 401 status code에는 인증 API 호출
RetryWhen #2
public Single<Profile> getProfile() {
return profileRepository.getProfile().retryWhen(errors ->
errors.zipWith(Flowable.range(0, LIMIT),
this::handleRetryAttempt).flatMap(x -> x));
}
private Flowable<Long> handleRetryAttempt(Throwable throwable, int attempt) {
if (throwable instanceof SocketTimeoutException) {
switch (attempt) {
case LIMIT:
return Flowable.error(throwable);
default:
return Flowable.timer(attempt * 3, TimeUnit.SECONDS);
}
} else {else if (throwable instanceof HttpException) {
// 다음 슬라이드
}
OkHttpClient Interceptor도 가능?
원래 흐름으로 돌아올 방법 없음
RetryWhen #3
private Flowable<Long> handleRetryAttempt(Throwable throwable, int attempt) {
if (throwable instanceof SocketTimeoutException) {
...
} else if (throwable instanceof HttpException) {
int statusCode = ((HttpException) throwable).code();
if (statusCode == 401) {
return profileRepository.refresh().retry((refreshAttempt, e) ->
refreshAttempt < LIMIT
&& e instanceof SocketTimeoutException)
.map(x -> 1L).toFlowable();
}
}
return Flowable.error(throwable);
}
RetryWhen #4
public class RetryTransformer<T> implements SingleTransformer<T, T> {
. . . .
@Override
public SingleSource apply(Single<T> upstream) {
return upstream.retryWhen(errors ->
errors.zipWith(Flowable.range(0, LIMIT),
this::handleRetryAttempt).flatMap(x -> x));
}
}
public Single<Profile> getProfile() {
return profileRepository.getProfile().compose(retryTransformer);
}
예외 처리 #1
public long getTimeInMillis1(String input) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
return sdf.parse(input).getTime();
}
public long getTimeInMillis2(String input) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
try {
return sdf.parse(input).getTime();
} catch (ParseException e) {
e.printStackTrace();
return -1L; // -1을 상수로 해도 큰 차이가 없음
}
}
예외 처리 #2
public long getTimeInMillis3(String input) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
try {
return sdf.parse(input).getTime();
} catch (ParseException e) {
throw new IllegalArgumentException("Illegal input pattern");
}
}
예외 처리 #3
public Single<Long> getTimeInMillis(String input) {
return Single.fromCallable(() -> {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
return sdf.parse(input).getTime();
});
}
String input = "2017-xx-30 02:20:20";
DateFormatter formatter = new DateFormatter();
long start = formatter.getTimeInMillis(input)
.onErrorReturnItem(0L)
.blockingGet();
long end = formatter.getTimeInMillis(input)
.onErrorReturnItem(new Date().getTime())
.blockingGet();
System.out.println("start=" + start + ", end=" + end);
flatMap BiFunction
Observable.range(2, 8)
.flatMap(row -> Observable.range(1, 9)
.map(col -> String.format("%d x %d = %d",
row, col, row * col)))
.subscribe(System.out::println);
Observable.range(2, 8)
.flatMap(row -> Observable.range(1, 9),
(row, col) -> String.format("%d x %d = %d",
row, col, row * col))
.subscribe(System.out::println);
collect
Observable<String> obs = Observable.fromIterable(searchOptions)
.filter(SearchOption::isInUse)
.map(SearchOption::getName);
obs.subscribe(name -> {
applySearchOptions.add(name);
}, e -> {
}, () -> {
showData(applySearchOptions);
});
Single<ArrayList<String>> obs = Observable.fromIterable(searchOptions)
.filter(SearchOption::isInUse)
.map(SearchOption::getName)
.collect(ArrayList<String>::new, List::add);
obs.subscribe(this::showData);
Q & A
드로이드 나이츠 2018: RxJava 적용 팁 및 트러블 슈팅

More Related Content

What's hot

Java practice programs for beginners
Java practice programs for beginnersJava practice programs for beginners
Java practice programs for beginnersishan0019
 
Обзор фреймворка Twisted
Обзор фреймворка TwistedОбзор фреймворка Twisted
Обзор фреймворка TwistedMaxim Kulsha
 
Ensure code quality with vs2012
Ensure code quality with vs2012Ensure code quality with vs2012
Ensure code quality with vs2012Sandeep Joshi
 
Simple API for XML
Simple API for XMLSimple API for XML
Simple API for XMLguest2556de
 
PyconKR 2018 Deep dive into Coroutine
PyconKR 2018 Deep dive into CoroutinePyconKR 2018 Deep dive into Coroutine
PyconKR 2018 Deep dive into CoroutineDaehee Kim
 
The Ring programming language version 1.3 book - Part 7 of 88
The Ring programming language version 1.3 book - Part 7 of 88The Ring programming language version 1.3 book - Part 7 of 88
The Ring programming language version 1.3 book - Part 7 of 88Mahmoud Samir Fayed
 
Use C++ to Manipulate mozSettings in Gecko
Use C++ to Manipulate mozSettings in GeckoUse C++ to Manipulate mozSettings in Gecko
Use C++ to Manipulate mozSettings in GeckoChih-Hsuan Kuo
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql JOYITAKUNDU1
 
The Ring programming language version 1.2 book - Part 16 of 84
The Ring programming language version 1.2 book - Part 16 of 84The Ring programming language version 1.2 book - Part 16 of 84
The Ring programming language version 1.2 book - Part 16 of 84Mahmoud Samir Fayed
 
The Ring programming language version 1.3 book - Part 17 of 88
The Ring programming language version 1.3 book - Part 17 of 88The Ring programming language version 1.3 book - Part 17 of 88
The Ring programming language version 1.3 book - Part 17 of 88Mahmoud Samir Fayed
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 SpringKiyotaka Oku
 
The Ring programming language version 1.6 book - Part 15 of 189
The Ring programming language version 1.6 book - Part 15 of 189The Ring programming language version 1.6 book - Part 15 of 189
The Ring programming language version 1.6 book - Part 15 of 189Mahmoud Samir Fayed
 
The Ring programming language version 1.5.3 book - Part 88 of 184
The Ring programming language version 1.5.3 book - Part 88 of 184The Ring programming language version 1.5.3 book - Part 88 of 184
The Ring programming language version 1.5.3 book - Part 88 of 184Mahmoud Samir Fayed
 
The Ring programming language version 1.9 book - Part 91 of 210
The Ring programming language version 1.9 book - Part 91 of 210The Ring programming language version 1.9 book - Part 91 of 210
The Ring programming language version 1.9 book - Part 91 of 210Mahmoud Samir Fayed
 
The Ring programming language version 1.10 book - Part 34 of 212
The Ring programming language version 1.10 book - Part 34 of 212The Ring programming language version 1.10 book - Part 34 of 212
The Ring programming language version 1.10 book - Part 34 of 212Mahmoud Samir Fayed
 
OSGi World Congress Workshop Exercise - P Kriens
OSGi World Congress Workshop Exercise - P KriensOSGi World Congress Workshop Exercise - P Kriens
OSGi World Congress Workshop Exercise - P Kriensmfrancis
 
The Ring programming language version 1.10 book - Part 94 of 212
The Ring programming language version 1.10 book - Part 94 of 212The Ring programming language version 1.10 book - Part 94 of 212
The Ring programming language version 1.10 book - Part 94 of 212Mahmoud Samir Fayed
 
The Ring programming language version 1.10 book - Part 38 of 212
The Ring programming language version 1.10 book - Part 38 of 212The Ring programming language version 1.10 book - Part 38 of 212
The Ring programming language version 1.10 book - Part 38 of 212Mahmoud Samir Fayed
 
The Ring programming language version 1.7 book - Part 16 of 196
The Ring programming language version 1.7 book - Part 16 of 196The Ring programming language version 1.7 book - Part 16 of 196
The Ring programming language version 1.7 book - Part 16 of 196Mahmoud Samir Fayed
 
Java 8 - Nuts and Bold - SFEIR Benelux
Java 8 - Nuts and Bold - SFEIR BeneluxJava 8 - Nuts and Bold - SFEIR Benelux
Java 8 - Nuts and Bold - SFEIR Beneluxyohanbeschi
 

What's hot (20)

Java practice programs for beginners
Java practice programs for beginnersJava practice programs for beginners
Java practice programs for beginners
 
Обзор фреймворка Twisted
Обзор фреймворка TwistedОбзор фреймворка Twisted
Обзор фреймворка Twisted
 
Ensure code quality with vs2012
Ensure code quality with vs2012Ensure code quality with vs2012
Ensure code quality with vs2012
 
Simple API for XML
Simple API for XMLSimple API for XML
Simple API for XML
 
PyconKR 2018 Deep dive into Coroutine
PyconKR 2018 Deep dive into CoroutinePyconKR 2018 Deep dive into Coroutine
PyconKR 2018 Deep dive into Coroutine
 
The Ring programming language version 1.3 book - Part 7 of 88
The Ring programming language version 1.3 book - Part 7 of 88The Ring programming language version 1.3 book - Part 7 of 88
The Ring programming language version 1.3 book - Part 7 of 88
 
Use C++ to Manipulate mozSettings in Gecko
Use C++ to Manipulate mozSettings in GeckoUse C++ to Manipulate mozSettings in Gecko
Use C++ to Manipulate mozSettings in Gecko
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql
 
The Ring programming language version 1.2 book - Part 16 of 84
The Ring programming language version 1.2 book - Part 16 of 84The Ring programming language version 1.2 book - Part 16 of 84
The Ring programming language version 1.2 book - Part 16 of 84
 
The Ring programming language version 1.3 book - Part 17 of 88
The Ring programming language version 1.3 book - Part 17 of 88The Ring programming language version 1.3 book - Part 17 of 88
The Ring programming language version 1.3 book - Part 17 of 88
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
 
The Ring programming language version 1.6 book - Part 15 of 189
The Ring programming language version 1.6 book - Part 15 of 189The Ring programming language version 1.6 book - Part 15 of 189
The Ring programming language version 1.6 book - Part 15 of 189
 
The Ring programming language version 1.5.3 book - Part 88 of 184
The Ring programming language version 1.5.3 book - Part 88 of 184The Ring programming language version 1.5.3 book - Part 88 of 184
The Ring programming language version 1.5.3 book - Part 88 of 184
 
The Ring programming language version 1.9 book - Part 91 of 210
The Ring programming language version 1.9 book - Part 91 of 210The Ring programming language version 1.9 book - Part 91 of 210
The Ring programming language version 1.9 book - Part 91 of 210
 
The Ring programming language version 1.10 book - Part 34 of 212
The Ring programming language version 1.10 book - Part 34 of 212The Ring programming language version 1.10 book - Part 34 of 212
The Ring programming language version 1.10 book - Part 34 of 212
 
OSGi World Congress Workshop Exercise - P Kriens
OSGi World Congress Workshop Exercise - P KriensOSGi World Congress Workshop Exercise - P Kriens
OSGi World Congress Workshop Exercise - P Kriens
 
The Ring programming language version 1.10 book - Part 94 of 212
The Ring programming language version 1.10 book - Part 94 of 212The Ring programming language version 1.10 book - Part 94 of 212
The Ring programming language version 1.10 book - Part 94 of 212
 
The Ring programming language version 1.10 book - Part 38 of 212
The Ring programming language version 1.10 book - Part 38 of 212The Ring programming language version 1.10 book - Part 38 of 212
The Ring programming language version 1.10 book - Part 38 of 212
 
The Ring programming language version 1.7 book - Part 16 of 196
The Ring programming language version 1.7 book - Part 16 of 196The Ring programming language version 1.7 book - Part 16 of 196
The Ring programming language version 1.7 book - Part 16 of 196
 
Java 8 - Nuts and Bold - SFEIR Benelux
Java 8 - Nuts and Bold - SFEIR BeneluxJava 8 - Nuts and Bold - SFEIR Benelux
Java 8 - Nuts and Bold - SFEIR Benelux
 

Similar to 드로이드 나이츠 2018: RxJava 적용 팁 및 트러블 슈팅

Similar to 드로이드 나이츠 2018: RxJava 적용 팁 및 트러블 슈팅 (20)

2 презентация rx java+android
2 презентация rx java+android2 презентация rx java+android
2 презентация rx java+android
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
 
Reactive Programming on Android
Reactive Programming on AndroidReactive Programming on Android
Reactive Programming on Android
 
RxJava on Android
RxJava on AndroidRxJava on Android
RxJava on Android
 
Rxjs ngvikings
Rxjs ngvikingsRxjs ngvikings
Rxjs ngvikings
 
Painless Persistence with Realm
Painless Persistence with RealmPainless Persistence with Realm
Painless Persistence with Realm
 
Jersey Guice AOP
Jersey Guice AOPJersey Guice AOP
Jersey Guice AOP
 
Rxjs swetugg
Rxjs swetuggRxjs swetugg
Rxjs swetugg
 
생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트
 
ClojureScript loves React, DomCode May 26 2015
ClojureScript loves React, DomCode May 26 2015ClojureScript loves React, DomCode May 26 2015
ClojureScript loves React, DomCode May 26 2015
 
Rxjs marble-testing
Rxjs marble-testingRxjs marble-testing
Rxjs marble-testing
 
Anti patterns
Anti patternsAnti patterns
Anti patterns
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on Android
 
Solr @ Etsy - Apache Lucene Eurocon
Solr @ Etsy - Apache Lucene EuroconSolr @ Etsy - Apache Lucene Eurocon
Solr @ Etsy - Apache Lucene Eurocon
 
Rx java in action
Rx java in actionRx java in action
Rx java in action
 
Java VS Python
Java VS PythonJava VS Python
Java VS Python
 
Rx workshop
Rx workshopRx workshop
Rx workshop
 
Ten useful JavaScript tips & best practices
Ten useful JavaScript tips & best practicesTen useful JavaScript tips & best practices
Ten useful JavaScript tips & best practices
 
Javascript
JavascriptJavascript
Javascript
 
#JavaFX.forReal() - ElsassJUG
#JavaFX.forReal() - ElsassJUG#JavaFX.forReal() - ElsassJUG
#JavaFX.forReal() - ElsassJUG
 

드로이드 나이츠 2018: RxJava 적용 팁 및 트러블 슈팅

  • 2. Rx Contract #1-1 no more messages should arrive after an onError or onComplete message RxView.clicks(button) .flatMap(ignored -> getBestSeller() .subscribeOn(Schedulers.io())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bookTitle -> title.setText(bookTitle), e -> Toast.makeText(this, "문제 발생", Toast.LENGTH_LONG).show() ); 무한 이벤트에서 문제 발생 가능
  • 3. Rx Contract #1-2 RxView.clicks(button) .flatMap(ignored -> getBestSeller() .subscribeOn(Schedulers.io()) .map(title -> new ViewState.Result(title)) // (1) .cast(ViewState.class) // (2) .onErrorReturn(e -> new ViewState.Error(e))) // (3) .observeOn(AndroidSchedulers.mainThread()) .subscribe(viewState -> { if (viewState instanceof ViewState.Result) { // (4) title.setText(((ViewState.Result) viewState).title); } else if (viewState instanceof ViewState.Error) { // (5) Toast.makeText(this, "문제 발생", Toast.LENGTH_LONG).show(); } });
  • 4. Rx Contract #1-3 interface ViewState { class Error implements ViewState { Throwable e; Error(Throwable e) { this.e = e; } } class Result implements ViewState { String title; Result(String title) { this.title = title; } } } http://hannesdorfmann.com/android/ mosby3-mvi-1 visual bugs such as displaying a loading indicator (“loading state”) and error indicator (“error state”) at the same time => exactly one output
  • 5. Rx Contract #2-1 스트림에 널(null) 허용 안됨 @Test(expected = NullPointerException.class) public void nullEvent() { Observable.just("Hello", null, "RxJava") .subscribe(System.out::println, System.err::println); } Observable 생성 에서 에러 발생
  • 6. Rx Contract #2-2 @Test public void nullEventOnCreate() { Observable.create(emitter -> { emitter.onNext("Hello"); emitter.onNext(null); emitter.onNext("RxJava"); }).subscribe(System.out::println, System.err::println); } java.lang.NullPointerException: onNext called with null. Null values are generally not allowed in 2.x operators and sources.
  • 7. Rx Contract #2-3 @Test public void nullEventPossible() { Observable.just(1, 2, -1, 1, 2) .map(dollar -> getCurrentPrice(dollar)) .subscribe(System.out::println, System.err::println); } private String getCurrentPrice(int dollar) { if (dollar < 0) { return null; } return (dollar * 1000) + " won"; } 1000 won 2000 won java.lang.NullPointerException: The mapper function returned a null value.
  • 8. Rx Contract #2-4 @Test public void nullEventPossible3() { Observable.just(1, 2, -1, 1, 2) .map(dollar -> getCurrentPrice3(dollar)) .onErrorReturnItem("0 won") .subscribe(System.out::println, System.err::println, () -> System.out.println("onComplete")); } private String getCurrentPrice3(int dollar) { if (dollar < 0) { throw new IllegalArgumentException("dollar should be bigger than 0"); } return (dollar * 1000) + " won"; } [1000 won, 2000 won, 0 won, onComplete]
  • 9. Rx Contract #2-5 @Test public void nullEventPossible4() { Observable.just(1, 2, -1, 1, 2) .flatMap(dollar -> getCurrentPrice4(dollar) .onErrorReturnItem("0 won")) // (1) .subscribe(System.out::println, System.err::println); } private Observable getCurrentPrice4(int dollar) { if (dollar < 0) { return Observable.error( new IllegalArgumentException("dollar should be bigger than 0")); // (2) } return Observable.just((dollar * 1000) + " won"); // (3) }
  • 10. Rx Contract #3-1 Assume observer instances are called in a serialized fashion Observable obs = Observable.create(emitter -> { new Thread(() -> { emitter.onNext(1); emitter.onNext(3); . . . . emitter.onNext(9); // emitter.onComplete(); // (1) }).start(); new Thread(() -> { emitter.onNext(2); emitter.onNext(4); . . . . emitter.onNext(10); // emitter.onComplete(); // (2) }).start(); }); onComplete 넣 을 수 없음
  • 11. Rx Contract #3-2 obs.subscribe(value -> System.out.println( Thread.currentThread().getName() + ":" + value), System.err::println, () -> System.out.println("onComplete")); onComplete 불 리지 않음
  • 12. Rx Contract #3-3 Observable obs1 = Observable.create(emitter -> { new Thread(() -> { emitter.onNext(1); . . . . emitter.onNext(9); emitter.onComplete(); // (1) }).start(); }); Observable obs2 = Observable.create(emitter -> { new Thread(() -> { emitter.onNext(2); . . . . emitter.onNext(10); emitter.onComplete(); // (2) }).start(); }); (1), (2) 모두 필요
  • 13. Rx Contract #3-4 Observable.merge(obs1, obs2).subscribe(value -> System.out.println(Thread.currentThread().getName() + ":" + value), System.err::println, () -> System.out.println("onComplete") ); onComplete 정 상 호출
  • 14. Rx Contract #3-5 Observable obs1 = Observable.create(emitter -> { emitter.onNext(1); emitter.onNext(3); . . . . emitter.onNext(9); emitter.onComplete(); }).subscribeOn(Schedulers.computation()); Observable obs2 = Observable.create(emitter -> { emitter.onNext(2); emitter.onNext(4); . . . . emitter.onNext(10); emitter.onComplete(); }).subscribeOn(Schedulers.computation());
  • 15. Rx Contract #3-6 Observable.merge(obs1, obs2).subscribe(value -> System.out.println( Thread.currentThread().getName() + ":" + value), System.err::println, () -> System.out.println("onComplete") );
  • 16. flatMap thread #1-1 repository.getBookCategories().toObservable() .flatMapIterable(categories -> categories) .flatMapSingle(category -> repository.getCategoryBooks(category.categoryId)) .subscribeOn(Schedulers.io()) .collect(ArrayList::new, ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString() repository.getBookCategories().toObservable() .subscribeOn(Schedulers.io()) .flatMapIterable(categories -> categories) .flatMapSingle(category -> repository.getCategoryBooks(category.categoryId) .subscribeOn(Schedulers.io())) .collect(ArrayList::new, ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString()));
  • 18. flatMap thread #2 단일 이벤트 연결 repository.getRegion(location) .map(region -> region.areaCode) .flatMap(repository::getWeather) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(weather -> title.setText(weather.toString())); repository.getRegion(location) .subscribeOn(Schedulers.io()) .map(region -> region.areaCode) .flatMap(areaCode -> repository.getWeather(areaCode) .subscribeOn(Schedulers.io())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(weather -> title.setText(weather.toString()));
  • 19. merge thread #1-1 Single.merge(repository.getBestSeller(), repository.getRecommendBooks(), repository.getCategoryBooks(7)) .subscribeOn(Schedulers.io()) .collect(ArrayList::new, ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString())); Single.merge(repository.getBestSeller().subscribeOn(Schedulers.io()), repository.getRecommendBooks().subscribeOn(Schedulers.io()), repository.getCategoryBooks(7).subscribeOn(Schedulers.io())) .collect(ArrayList::new, ArrayList::addAll) .observeOn(AndroidSchedulers.mainThread()) .subscribe(books -> title.setText(books.toString()));
  • 21. zip thread #1-1 Single.zip(repository.getWeather(regionCode), repository.getWeatherDetail(regionCode), Pair::new) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(pair -> title.setText(pair.first + ", " + pair.second)); Single.zip(repository.getWeather(regionCode).subscribeOn(Schedulers.io()), repository.getWeatherDetail(regionCode).subscribeOn(Schedulers.io()), Pair::new) .observeOn(AndroidSchedulers.mainThread()) .subscribe(pair -> title.setText(pair.first + ", " + pair.second));
  • 23. RetryWhen #1 - SocketTimeout에 재시도(3번 재시도) - 401 status code에는 인증 API 호출
  • 24. RetryWhen #2 public Single<Profile> getProfile() { return profileRepository.getProfile().retryWhen(errors -> errors.zipWith(Flowable.range(0, LIMIT), this::handleRetryAttempt).flatMap(x -> x)); } private Flowable<Long> handleRetryAttempt(Throwable throwable, int attempt) { if (throwable instanceof SocketTimeoutException) { switch (attempt) { case LIMIT: return Flowable.error(throwable); default: return Flowable.timer(attempt * 3, TimeUnit.SECONDS); } } else {else if (throwable instanceof HttpException) { // 다음 슬라이드 } OkHttpClient Interceptor도 가능? 원래 흐름으로 돌아올 방법 없음
  • 25. RetryWhen #3 private Flowable<Long> handleRetryAttempt(Throwable throwable, int attempt) { if (throwable instanceof SocketTimeoutException) { ... } else if (throwable instanceof HttpException) { int statusCode = ((HttpException) throwable).code(); if (statusCode == 401) { return profileRepository.refresh().retry((refreshAttempt, e) -> refreshAttempt < LIMIT && e instanceof SocketTimeoutException) .map(x -> 1L).toFlowable(); } } return Flowable.error(throwable); }
  • 26. RetryWhen #4 public class RetryTransformer<T> implements SingleTransformer<T, T> { . . . . @Override public SingleSource apply(Single<T> upstream) { return upstream.retryWhen(errors -> errors.zipWith(Flowable.range(0, LIMIT), this::handleRetryAttempt).flatMap(x -> x)); } } public Single<Profile> getProfile() { return profileRepository.getProfile().compose(retryTransformer); }
  • 27. 예외 처리 #1 public long getTimeInMillis1(String input) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); return sdf.parse(input).getTime(); } public long getTimeInMillis2(String input) { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); try { return sdf.parse(input).getTime(); } catch (ParseException e) { e.printStackTrace(); return -1L; // -1을 상수로 해도 큰 차이가 없음 } }
  • 28. 예외 처리 #2 public long getTimeInMillis3(String input) { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); try { return sdf.parse(input).getTime(); } catch (ParseException e) { throw new IllegalArgumentException("Illegal input pattern"); } }
  • 29. 예외 처리 #3 public Single<Long> getTimeInMillis(String input) { return Single.fromCallable(() -> { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); return sdf.parse(input).getTime(); }); } String input = "2017-xx-30 02:20:20"; DateFormatter formatter = new DateFormatter(); long start = formatter.getTimeInMillis(input) .onErrorReturnItem(0L) .blockingGet(); long end = formatter.getTimeInMillis(input) .onErrorReturnItem(new Date().getTime()) .blockingGet(); System.out.println("start=" + start + ", end=" + end);
  • 30. flatMap BiFunction Observable.range(2, 8) .flatMap(row -> Observable.range(1, 9) .map(col -> String.format("%d x %d = %d", row, col, row * col))) .subscribe(System.out::println); Observable.range(2, 8) .flatMap(row -> Observable.range(1, 9), (row, col) -> String.format("%d x %d = %d", row, col, row * col)) .subscribe(System.out::println);
  • 31. collect Observable<String> obs = Observable.fromIterable(searchOptions) .filter(SearchOption::isInUse) .map(SearchOption::getName); obs.subscribe(name -> { applySearchOptions.add(name); }, e -> { }, () -> { showData(applySearchOptions); }); Single<ArrayList<String>> obs = Observable.fromIterable(searchOptions) .filter(SearchOption::isInUse) .map(SearchOption::getName) .collect(ArrayList<String>::new, List::add); obs.subscribe(this::showData);
  • 32. Q & A