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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Naoto Nakazato
April 19, 2017
Technology
1
2.7k
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
750
Other Decks in Technology
See All in Technology
複数クラスタ運用と検索の高度化:ビズリーチにおけるElastic活用事例 / ElasticON Tokyo2026
visional_engineering_and_design
0
160
JAWS DAYS 2026 楽しく学ぼう!ストレージ 入門
yoshiki0705
2
190
Kubernetesにおける推論基盤
ry
1
380
プラットフォームエンジニアリングはAI時代の開発者をどう救うのか
jacopen
5
2.2k
社内レビューは機能しているのか
matsuba
0
130
僕、S3 シンプルって名前だけど全然シンプルじゃありません よろしくお願いします
yama3133
1
220
Claude Codeが爆速進化してプラグイン追従がつらいので半自動化した話 ver.2
rfdnxbro
0
530
ガバメントクラウドにおけるAWSの長期継続割引について
takeda_h
2
140
ランサムウエア対策してますか?やられた時の対策は本当にできてますか?AWSでのリスク分析と対応フローの泥臭いお話。
hootaki
0
130
20260311 技術SWG活動報告(デジタルアイデンティティ人材育成推進WG Ph2 活動報告会)
oidfj
0
350
猫でもわかるKiro CLI(AI 駆動開発への道編)
kentapapa
0
210
マルチプレーンGPUネットワークを実現するシャッフルアーキテクチャの整理と考察
markunet
2
250
Featured
See All Featured
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
640
Building a Modern Day E-commerce SEO Strategy
aleyda
45
8.9k
Amusing Abliteration
ianozsvald
0
130
Producing Creativity
orderedlist
PRO
348
40k
Darren the Foodie - Storyboard
khoart
PRO
3
2.9k
How to Ace a Technical Interview
jacobian
281
24k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.7k
BBQ
matthewcrist
89
10k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
140
So, you think you're a good person
axbom
PRO
2
2k
Technical Leadership for Architectural Decision Making
baasie
3
290
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化も ご清聴ありがとうございました