본 포스팅은 DroidKaigi 2017 ~ 全てSになる -RxJavaとLWSを持ち込む楽しさ 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
DROIDKAIGi 2017 / DAY.1 / RYUTARO MIYASHITA
ryugoo / 宮下竜太郎
ChatWork 애플리케이션 개발부 리더
전부 S
가 된다
전부 Stream
가 된다
Java 8
에서는 다양한 언어 기능이 강화되어있다
// Lambda 식
view.setOnClickListener(v -> {
})
// Method Reference
view.setOnClickListener(this::proc);
public void proc(View v) {}
// Stream/Optional API
Stream.of(Arrays.asList("1", "2", "3"));
Optional.ofNullable(getSupportActionBar());
Android N 과 함께 Android 에도 Java 8
이 왔다
android {
defaultConfig {
jackOPtions { enabled true }
}
}
compatibilityOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
Jack is not
silver bullet
API Lv.24이상
이 필요합니다
다음 생애
?① : https://github.com/sys1yagi/android-method-counts
② : https://developer.android.com/about/dashboards/index.html
미래를 선취(先取り)
한다
Retrolambda
RxJava
Lightweight Stream API
Retrolambda
RxJava
Lightweight Stream API
Lambda 식과 Reference Method 전제로 이야기를 하겠습니다
Lambda 식
Reference Method
손에 넣는 것은 언어 기능 부분
https://github.com/evant/gradle-retrolambda
Java 8의 Lambda 식을 Android 에
buildscript {
repositories { mavenCentral() }
dependencies {
classpath 'me.tatarka:gradle-retrolambda:3.6.0'
}
}
repositories { mavenCentral() }
apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'
Jack과 동일하게 compatibilityOptions에 Java 8을 지정한다
android {
defaultConfig {
jackOPtions { enabled false }
}
}
compatibilityOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
Retrolambda
RxJava
Lightweight Stream API
Reactive Extensions for JVM
Stream API
도 가능합니다
https://github.com/ReactiveX/RxJava
Reactive Extensions for JVM
① : https://github.com/JakeWharton/RxBinding
② : https://github.com/ReactiveX/RxAndroid
③ : https://github.com/trello/RxLifecycle
Java8 Stream API 같은 Collection 조작
// RxJava
Observable.fromArray("1", "2", "3")
.map(Integer::valueOf)
.filter(num -> num % 2 == 0)
.toList()
.blockingGet(); // [2]
// Stream
Stream.of("1", "2", "3")
.map(Integer::valueOf)
.filter(num -> num % 2 == 0)
.toList(); // [2]
심플한 비동기 처리
Single.fromCallable(() -> {
Thread.sleep(2000L);
return "finish";
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
text -> Log.d(TAG, text);
Throwable::printStackTrace
);
Single.fromCallable(() -> {
Thread.sleep(2000L);
return "finish";
})
.subscribeOn(Schedulers.computation())
프리셋된 ThreadPool을 사용해 Single#fromCallable 처리를 실행
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
text -> Log.d(TAG, text);
Throwable::printStackTrace
);
RxAndroid를 사용해 UI 스레드로 결과를 취득
동일한 처리를 AsyncTask로 적으면
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... parmas) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrance();
}
return "finish";
}
@Override
protected void doPostExecute(String text) {
Log.d(TAG, text);
}
}.execute();
Single.fromCallable(() -> {
Thread.sleep(2000L);
return "finish";
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
text -> Log.d(TAG, text);
Throwable::printStackTrace
);
Use case : 버튼을 누르면 Web API와 통신해서 결과를 표시
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AsyncTask<String, Void, String>() {
@Override
protected String doInBackground(String... parmas) {
return new WebAPIClient(params[0]);
}
@Override
protected void doPostExecute(String result) {
Log.d(TAG, result);
}
}.execute("Send parameter");
}
});
Use case : 버튼을 누르면 Web API와 통신해서 결과를 표시
final String params = "Send parameter";
RxView.clicks(findViewById(R.id.button))
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.computation())
.map(event -> new WebAPIClient(params))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
result -> Log.d(TAG, result),
Throwable::printStackTrace
);
RxView.clicks(findViewById(R.id.button))
.subscribeOn(AndroidSchedulers.mainThread())
RxBinding을 사용해 클릭 이벤트를 RxJava로 처리 (UI Thread)
.observeOn(Schedulers.computation())
아래의 처리를 Worker Thread 로 전환
.map(event -> new WebAPIClient(params))
클릭 이벤트를 Web API 통신 결과로 변환
.observeOn(AndroidSchedulers.mainThread())
아래 처리는 UI Thread로 전환
.subscribe(
result -> Log.d(TAG, result),
Throwable::printStackTrace
);
결과 취득과 처리
Use case : 버튼을 누르면 Web API와 통신해서 결과를 표시
final String params = "Send parameter";
RxView.clicks(findViewById(R.id.button))
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.computation())
.map(event -> new WebAPIClient(params))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
result -> Log.d(TAG, result),
Throwable::printStackTrace
);
Stream
Retrolambda
RxJava
Lightweight Stream API
Stream API from Java 8 rewritten on iterators for Java 7 and below
Optional
이나 Objects도 있습니다자체적으로 확장된 API
가 있습니다Stream API from Java 8 rewritten on iterators for Java 7 and below
Stream API
Optional API
Exceptional API
(자체)
Stream API
Stream<String> stream = Stream.of(3, 3, 3);
// collect는 단 1번만
stream.collect(Collectors.toList()); // [3,3,3]
stream.collect(Collectors.toSet()); // (3)
stream.collect(Collectors.toMap( // {"3"=3}
String::valueOf,
num -> num,
HashMap::new
));
stream.map(String::valueOf)
.collect(Collectors.joining(",")); // 3,3,3
Optional API
// 기존의 작성 방법
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { actionBar.setTitle("Hello"); }
// Optional을 사용한 작성 방법
Optional.ofNullable(getSupportActionBar())
.ifPresent(actionBar -> actionBar.setTitle("Hello"));
Use case: FragmentManagere#findFragmentByTag
FragmentManager fm = getSupportFragmentManager();
Fragment f = fm.findFragmentByTag(FTAG);
MyFragment myF;
if (f != null && f instanceof MyFragment) {
myF = (MyFragment) f;
} else {
myF = MyFragment.newInstance();
}
MyFramgnet 인스턴스를 얻을 때까지의 처리를 절차적으로 기술합니다
Use case: FragmentManagere#findFragmentByTag
FragmentManager fm = getSupportFragmentManager();
MyFragment myF =
Optional.ofNullable(fm.findFragmentByTag(FTAG))
.filter(MyFragment.class::isInstance)
.my(f -> (MyFragment) f)
.orElseGet(MyFragment::newInstance);
MyFramgnet 인스턴스를 얻을 때까지의 흐름을 선언적으로 기술 가능합니다.
Use case: FragmentManagere#findFragmentByTag
FragmentManager fm = getSupportFragmentManager();
MyFragment myF =
Optional.ofNullable(fm.findFragmentByTag(FTAG))
.select(MyFragment.class) // LWS 자체
.orElseGet(MyFragment::newInstance);
MyFramgnet 인스턴스를 얻을 때까지의 흐름을 선언적으로 기술 가능합니다.
Optional API의 주의점
// 위험
private Optional<String> optString;
optString.ifPresent(text -> {}); // NPE
// 초기화시에 empty를 대입한다
private Optional<String> optString = Optional.empty();
optString.ifPresent(text -> {}); // Works
The fun of Lightweight Stream API
Optional
은 NPE와 싸우는 강력한 무기가 됩니다
RxJava + Lightweight Stream API
Error handling problem of RxJava
protected void onCreate(Bundle saveInstanceState) {
~~~
RxView.clicks(findViewById(R.id.button))
.subscribeOn(AndroidSchedulers.mainThread())
.map(event -> {
throw new IllegalStateException("Exception");
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(success -> {
Log.d(TAG, "Success");
}, error -> {
Log.d(TAG, "error");
});
}
.map(event -> {
throw new IllegalStateException("Exception");
})
도중에 에러가 발생한 경우
}, error -> {
Log.d(TAG, "error");
});
onError가 실행되 “구독이 종료” 된다
Error handling problem of RxJava
onError가 오면 일련의 구독은 종료
합니다예상내의 실패
와 의도하지 않은 예외
를 나누고 싶다
More better RxJava error handling
public final class Either<Left, Right> {
private final Optional<Left> left;
private final Optional<Right> right;
private Either(Optional<Left> left, Optional<Right> right) {
this.left = left;
this.right = right;
}
public static Either<Left, Right> left(@NonNull Left value) {
return new Either<>(Optional.of(value), Optional.empty());
}
public static Either<Left, Right> right(@NonNull Right value) {
return new Either<>(Optional.empty(), Optional.of(value));
}
public <T> Either<T, Right> mapLeft(<Function<? super Left, ? extends T> func>) {
return new Either<>(this.left.map(func), this.right);
}
public <T> Either<T, Right> mapRight(<Function<? super Right, ? extends T> func>) {
return new Either<>(this.left, this.right.map(func));
}
public void apply(Consumer<? super Left> lConsumer, Consumer<? super Right> rConsumer) {
this.left.ifPresent(lConsumer);
this.right.ifPresent(rConsumer);
}
}
https://gist.github.com/ryugoo/8de36d41c249165b0b3344ba8ccbcf59
More better RxJava error handling
public Observable<Either<Throwable, String>> webApiRequest() {
return Observable.create(emitter -> {
try {
APIClient.request(success -> {
if (success) {
emitter.onNext(Either.right("Success"));
} else {
emitter.onNext(Either.left(new RuntimeException()));
}
});
} catch (IOException e) {
emitter.onError(e);
}
});
}
public Observable<Either<Throwable, String>> webApiRequest() {
올바른 값은 String, 실패의 값은 Throwable를 가지는 Either 타입을 알리는 Observable을 정의한다
if (success) {
emitter.onNext(Either.right("Success"));
요청 성공의 경우는 Either#right 를 알림
} else {
emitter.onNext(Either.left(new RuntimeException()));
요청 실패의 경우는 Either#left 를 알림
} catch (IOException e) {
emitter.onError(e);
의도하지 않은 실패의 경우는 onError로 예외를 알림
More better RxJava error handling
webApiRequest()
.subscribeOn(Schedules.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(either -> either.apply(
failure -> {}, // API Request 의 실패
success -> {}), // API Request 의 성공
error -> {} // API Request 이외에서 발생한 예외
);
.subscribe(either -> either.apply(
failure -> {}, // API Request 의 실패
success -> {}), // API Request 의 성공
error -> {} // API Request 이외에서 발생한 예외
);
“예상 범위 내의 실패”를 얻도록 된다
전부 Stream
가 된다
Stream
가 된다// Stream
List<String> result = Stream.of(1, 2, 3)
.filterNot(num -> num % 2 == 0)
.map(String::valueOf)
.toList();
↓
// Optional
MyFragment myFragment =
Optional.ofNullable(getSupportFragmentManager().findFragmentByTag("Fragment))
.select(MyFragment.class)
.orElseGet(MyFragment::newInstance);
↓
// RxJava
Single.fromCallable(() -> {
Thread.sleep(2000L);
return "finish";
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
System.out::println,
Throwable::printStackTrace
);
Stream
가 된다Stream
가 된다S
가 된다Demo apps
https://github.com/ryugoo/StreamDemo
comments powered by Disqus
Subscribe to this blog via RSS.