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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
Frontier Agents (Kiro autonomous agent / AWS Security Agent / AWS DevOps Agent) の紹介
msysh
3
190
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
15
93k
Tebiki Engineering Team Deck
tebiki
0
24k
Why Organizations Fail: ノーベル経済学賞「国家はなぜ衰退するのか」から考えるアジャイル組織論
kawaguti
PRO
1
200
私たち準委任PdEは2つのプロダクトに挑戦する ~ソフトウェア、開発支援という”二重”のプロダクトエンジニアリングの実践~ / 20260212 Naoki Takahashi
shift_evolve
PRO
2
210
Amazon Bedrock Knowledge Basesチャンキング解説!
aoinoguchi
0
160
Ruby版 JSXのRuxが気になる
sansantech
PRO
0
170
Kiro IDEのドキュメントを全部読んだので地味だけどちょっと嬉しい機能を紹介する
khmoryz
0
210
ブロックテーマでサイトをリニューアルした話 / 2026-01-31 Kansai WordPress Meetup
torounit
0
480
20260208_第66回 コンピュータビジョン勉強会
keiichiito1978
0
200
こんなところでも(地味に)活躍するImage Modeさんを知ってるかい?- Image Mode for OpenShift -
tsukaman
1
170
広告の効果検証を題材にした因果推論の精度検証について
zozotech
PRO
0
210
Featured
See All Featured
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
0
260
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
290
ラッコキーワード サービス紹介資料
rakko
1
2.3M
Marketing to machines
jonoalderson
1
4.6k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
430
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
920
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.3k
AI: The stuff that nobody shows you
jnunemaker
PRO
2
270
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
190
Crafting Experiences
bethany
1
52
sira's awesome portfolio website redesign presentation
elsirapls
0
150
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化も ご清聴ありがとうございました