Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
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.6k
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
非CUDAの悲哀 〜Claude Code と挑んだ image to 3D “Hunyuan3D”を EVO-X2(Ryzen AI Max+395)で動作させるチャレンジ〜
hawkymisc
1
170
[CMU-DB-2025FALL] Apache Fluss - A Streaming Storage for Real-Time Lakehouse
jark
0
120
Kiro Autonomous AgentとKiro Powers の紹介 / kiro-autonomous-agent-and-powers
tomoki10
0
430
AI 駆動開発勉強会 フロントエンド支部 #1 w/あずもば
1ftseabass
PRO
0
340
技術以外の世界に『越境』しエンジニアとして進化を遂げる 〜Kotlinへの愛とDevHRとしての挑戦を添えて〜
subroh0508
1
430
法人支出管理領域におけるソフトウェアアーキテクチャに基づいたテスト戦略の実践
ogugu9
1
220
エンジニアとPMのドメイン知識の溝をなくす、 AIネイティブな開発プロセス
applism118
4
1.2k
[JAWS-UG 横浜支部 #91]DevOps Agent vs CloudWatch Investigations -比較と実践-
sh_fk2
1
250
A Compass of Thought: Guiding the Future of Test Automation ( #jassttokai25 , #jassttokai )
teyamagu
PRO
1
260
Databricks向けJupyter Kernelでデータサイエンティストの開発環境をAI-Readyにする / Data+AI World Tour Tokyo After Party
genda
1
100
Overture Maps Foundationの3年を振り返る
moritoru
0
180
ChatGPTで論⽂は読めるのか
spatial_ai_network
8
26k
Featured
See All Featured
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
A better future with KSS
kneath
240
18k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.6k
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
Music & Morning Musume
bryan
46
7k
Bash Introduction
62gerente
615
210k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.4k
It's Worth the Effort
3n
187
29k
Rails Girls Zürich Keynote
gr2m
95
14k
KATA
mclloyd
PRO
32
15k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
1k
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化も ご清聴ありがとうございました