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()
);
무한 이벤트에서 문제 발생 가능
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 넣
을 수 없음
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);