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.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
740
Other Decks in Technology
See All in Technology
「どこにある?」の解決。生成AI(RAG)で効率化するガバメントクラウド運用
toru_kubota
2
370
評価の納得感を2段階高める「構造化フィードバック」
aloerina
1
150
研究開発部メンバーの働き⽅ / Sansan R&D Profile
sansan33
PRO
3
17k
基調講演: 生成AIを活用したアプリケーションの開発手法とは?
asei
1
120
堅牢な認証基盤の実現 TypeScriptで代数的データ型を活用する
kakehashi
PRO
2
210
SFTPコンテナからファイルをダウンロードする
dip
0
110
QAはソフトウェアエンジニアリングを学んで実践するのが大事なの
ymty
1
380
(新URLに移行しました)FASTと向き合うことで見えた、大規模アジャイルの難しさと楽しさ
wooootack
0
680
Amazon Q Developer for GitHubとAmplify Hosting でサクッとデジタル名刺を作ってみた
kmiya84377
0
2.6k
データ戦略部門 紹介資料
sansan33
PRO
1
3.2k
工具人的一生: 開發很多 AI 工具讓我 慵懶過一生
line_developers_tw
PRO
0
140
Model Mondays S2E01: Advanced Reasoning
nitya
0
310
Featured
See All Featured
Build your cross-platform service in a week with App Engine
jlugia
231
18k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
16
910
Faster Mobile Websites
deanohume
307
31k
Embracing the Ebb and Flow
colly
86
4.7k
Music & Morning Musume
bryan
46
6.6k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
22k
Rebuilding a faster, lazier Slack
samanthasiow
81
9k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
2.8k
Navigating Team Friction
lara
186
15k
A Tale of Four Properties
chriscoyier
159
23k
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化も ご清聴ありがとうございました