Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
RxJavaを1年使って見えてきたこと
Search
Naoto Nakazato
April 19, 2017
Technology
1
2.5k
RxJavaを1年使って見えてきたこと
CAMPFIRE Android #1で発表した「RxJavaを1年使って見えてきたこと」のスライドです
https://yj-meetup.connpass.com/event/53419/
Naoto Nakazato
April 19, 2017
Tweet
Share
More Decks by Naoto Nakazato
See All by Naoto Nakazato
Image Keyboardをいらすとやで作ってみた
oxsoft
3
720
Other Decks in Technology
See All in Technology
VideoMamba: State Space Model for Efficient Video Understanding
chou500
0
190
CDCL による厳密解法を採用した MILP ソルバー
imai448
3
180
安心してください、日本語使えますよ―Ubuntu日本語Remix提供休止に寄せて― 2024-11-17
nobutomurata
1
1k
Storybook との上手な向き合い方を考える
re_taro
4
370
EventHub Startup CTO of the year 2024 ピッチ資料
eventhub
0
130
The Rise of LLMOps
asei
9
1.7k
Flutterによる 効率的なAndroid・iOS・Webアプリケーション開発の事例
recruitengineers
PRO
0
120
OS 標準のデザインシステムを超えて - より柔軟な Flutter テーマ管理 | FlutterKaigi 2024
ronnnnn
1
300
オープンソースAIとは何か? --「オープンソースAIの定義 v1.0」詳細解説
shujisado
10
1.3k
IBC 2024 動画技術関連レポート / IBC 2024 Report
cyberagentdevelopers
PRO
1
120
Terraform Stacks入門 #HashiTalks
msato
0
360
[CV勉強会@関東 ECCV2024 読み会] オンラインマッピング x トラッキング MapTracker: Tracking with Strided Memory Fusion for Consistent Vector HD Mapping (Chen+, ECCV24)
abemii
0
230
Featured
See All Featured
For a Future-Friendly Web
brad_frost
175
9.4k
The Art of Programming - Codeland 2020
erikaheidi
52
13k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
44
2.2k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.1k
Gamification - CAS2011
davidbonilla
80
5k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
0
110
Designing for Performance
lara
604
68k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Ruby is Unlike a Banana
tanoku
97
11k
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
Designing on Purpose - Digital PM Summit 2013
jponch
115
7k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
42
9.2k
Transcript
RxJavaを1年使って見えてきたこと Naoto Nakazato
自己紹介 • Naoto Nakazato • Yahoo Japan Corporation • Yahoo!知恵袋
• アカウント ◦ Twitter: @oxsoft ◦ Facebook: naoto.nakazato ◦ GitHub: oxsoft ◦ Qiita: oxsoft
Reactiveしてますか? いまやアプリに欠かせない存在に
今日お話しすること • Yahoo!知恵袋のフルリニューアルから1年経った • RxJavaを導入したので、その影響を振り返ってみる • 恩恵を受けていること & こうしておけば良かったこと • 抽象的な設計論というより、具体的なコードの話 •
コードは基本的にRxJava2ではなくRxJava1がベースとなっています
基本構成
アプリの基本構成 • 2015年11月に1からリニューアル • 主なOSS ◦ RxJava1/RxAndroid ◦ Gson ◦
Lombok ◦ AndroidAnnotations ◦ Retrolambda ◦ Glide
アプリの基本構成 • Application ◦ Modelのインスタンスを保持 • Model ◦ データの保持(BehaviorSubject) ◦
APIリクエストの組み立てと実行 ◦ APIレスポンスを内部で使う Beanクラスに変換 • Activity ◦ Application経由でModelにアクセス ◦ ユーザーアクションに応じて Modelにリクエスト ◦ ModelからObservableを取得し表示系を更新する • ApiRequest/ApiResponse ◦ Modelのみ利用 • Bean ◦ ModelからActivityに情報を渡すのに利用 API Model Activity Request Response Bean
良かったこと
RxAndroidを使って良かった2つのこと • http://qiita.com/oxsoft/items/9ae07c5512449b15b923 • 2016年5月に書いた記事 • 非同期処理が楽 • 複数個所で最新の情報を表示
非同期処理が楽 public Single<Result> request(Param... params) { return Single.create((Single.OnSubscribe<Result>) singleSubscriber ->
{ // 非同期処理 try { singleSubscriber.onSuccess(new Result()); } catch (Exception e) { singleSubscriber.onError(e); } }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); } API Request 通信処理(非同期処理)をRxでラップしている OSSを使えば勝手にやってくれることも多い
非同期処理が楽 public Single<Bean> request(Param... params) { return request(...).map(result -> bean);
} Model request(...).subscribe(bean -> { // 終わった後の処理 }, throwable -> { // 例外処理 }); Activity レスポンスクラスをデータクラスに変換(腐敗防止層的なこと) 適宜キャッシュを返したりする データを受け取って、表示更新処理などを行う。エラー時の挙動も書く
複数箇所で最新の情報を表示 private BehaviorSubject<String> studentName = BehaviorSubject.create(); private void setName(String name)
{ studentName.onNext(name); } public String getName() { return studentName.getValue(); } public Observable<String> getNameObservable() { return studentName.asObservable(); } ModelにBehaviorSubjectを保持し、観測用の関数を用意する ”観測可能な”変数のように扱える
doOnTerminate() for Single public static <T> Single<T> doOnTerminate(Single<T> single, Action0
onTerminate) { return Single.create(singleSubscriber -> single.subscribe(value -> { onTerminate.call(); singleSubscriber.onSuccess(value); }, error -> { onTerminate.call(); singleSubscriber.onError(error); })); } 成功しても失敗しても実行する処理を書く(ObservableにあるものをSingleに実装) ※RxJava1ではSingleのlift()がprivateなので、上記のような実装をしています
ProgressDialogを簡単に出す public <T> Single<T> progress(Single<T> single) { ProgressDialog progressDialog =
new ProgressDialog(this); progressDialog.show(); return doOnTerminate(single, progressDialog::dismiss); } 以下のようにすれば、簡単にProgressDialogが表示できる progress(request(...)).subscribe(...)
二重をリクエスト防止する private List<Requestable> requests = new ArrayList<>(); protected <T> Single<T>
singleRequest(Requestable<T> requestable) { if (requests.contains(requestable)) { return Single.error(new DuplicateRequestException()); } requests.add(requestable); return doOnTerminate(requestable.request(), () -> requests.remove(requestable)); } • リクエスト中でなければ、リクエストする • リクエスト中ならば、エラーを返す
こうしておけば良かった
通信要求と通信状態は分けるべきだった Single<SearchResult> search(String query) 以下のような関数がModelにある あるいは以下のように、リクエストと結果が分かれている場合もある Single<Void> requestProfile(int userId) Observable<Profile>
observeProfile(int userId)
通信要求と通信状態は分けるべきだった シンプルだけど…… 画面回転などをした時に通信状態が引き継げなくなる
通信要求と通信状態は分けるべきだった void requestProfile(int userId) Observable<RequestState> observeProfileRequestState(int userId) Observable<Profile> observeProfile(int userId)
リクエストの要求、リクエスト状態の監視、結果の監視に分けると良さそう リクエスト状態とリクエスト結果を分けているのは、 キャッシュを即座に表示しつつ、最新の情報を取得する時などに有効
unsubscribeする場所が良くなかった onCreateで通信を開始している それに対応する形でonDestroyでunsubscribeしていた → GlideがIllegalArgumentExceptionで怒る → 表示系の購読/解除はonStart/onStopが良さそう
unsubscribeする場所が良くなかった protected void onCreate(Bundle savedInstanceState) { model.requestProfile(userId); } protected void
onStart() { model.observeProfileRequestState(userId).subscribe(requestState -> {...}); model.observeProfile(userId).subscribe(profile -> {...}); } protected void onStop() { unsubscribe(); } ※ただしDialogFragmentの表示は、onSaveInstanceStateの後だとダメなので、 タイミングをずらすなど別途対策をする必要があります
ストリームに流す値はimmutableにすべきだった • オブジェクトを受け取った側で状態を変更するのがご法度なのは当たり前 • インスタンスを使いまわすとdistinctUntilChangedなどがおかしくなる public void updateStudentName(String name) {
Student student = studentSubject.getValue(); student.setName(name); // Student is mutable! studentSubject.onNext(student); } ※distinctは内部でHashSet(ハッシュ値)を使っているのでこの問題が顕在化しにくいです
ストリームに流す値はimmutableにすべきだった BehaviorSubjectを細かく切ると良さそう APIごとに得られる情報をベン図にし、それぞれの領域にSubjectを用意する 細かくしすぎると管理が複雑になるので、限度は必要 例えば右図では、 API1のレスポンスでXとYのSubjectに値を流し、 API2のレスポンスでYとZのSubjectに値を流す API1:ユーザのツイート一覧 API2:ユーザのプロフィール X:ツイート情報
Y:ユーザの概要 Z:ユーザの詳細 など
ストリームにnullを流すべきではなかった • @NonNullや@Nullableが使えない • でも毎回nullチェックをするのは正直アホらしい • RxJava2に移行できない • Single<Void>はCompletableなどが用意されている •
Kotlinを使った方がいいかも
Listの更新方法を統一すべきだった 一番下までスクロールした時に追加のコンテンツを読み込む画面で、 追加分のデータしか通知しなかった Single<Result[]> requestMore() void requestMore() Observable<Result[]> observeResult() 毎回全データを返却した方が、画面回転や、ページ管理などで有利になる
RecyclerViewを積極的に使うべきだった NestedScrollView+LinearLayoutを使って実装を進めていくうちに、 気づいたらリストっぽいものであることに気がついた 要素の挿入や、内容の変更がとても大変になってしまった (diffを取るクラスを実装したり、各要素をObservableで管理したり……)
RecyclerViewを積極的に使うべきだった “要素数が不定”ならRecyclerViewの利用を考え始めたほうが良さそう 先ほどのように全データを毎回通知すれば、 DiffUtilを使うことでいい感じにアニメーションしてくれる DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(...); diffResult.dispatchUpdatesTo(adapter); //
adapterに通知
まとめ • 通信要求と通信状態は分ける • unsubscribeする場所をよく考える • ストリームにはimmutableを流す • ストリームにはnullは流さない •
Listっぽいものの扱いに注意する • RxJava2に移行する • ついでにKotlin化も ご清聴ありがとうございました