2024.10.01
自社の社内情報を未来の“ゴミ”にしないための備え 「情報量が多すぎる」時代がもたらす課題とは?
Android Architecture Componentを使ってリファクタリングした話(全1記事)
リンクをコピー
記事をブックマーク
Yoshihisa氏:では、よろしくお願いします。今日はAndroid Architecture Componentsを使ってリファクタリングした話をします。
Takeda Yoshihisaと申します。
今、株式会社Diverseのマッチングアプリの「YYC」のAndroidアプリを開発していて、Androidデビューから1年経過しました。あと光の戦士をやっています。
今日は、マッチングアプリでよくある、ユーザー間のメッセージをやりとりする履歴を表示する画面のリファクタリングをした話をします。ユーザー履歴の画面なんですが、ユーザー間のメッセージのやりとりをチャット風に表示する画面です。
この画面はユーザーの状態、性別、18歳以上の確認が済んでいるかどうかとか、これまでやりとりをしたことがある人かどうかで、いろいろ表示が変わります。ちなみに、このスクリーンショットは女性、年齢確認済み、相手の男性にこれまで一度もメールを送ったことがないという人です。
さっきのこのスタンプのパレットが表示される前の構造はこんな感じになっています。
Activityの上にRecyclerViewがいて、そこに表示するかたちになっている。あと、メッセージの入力フォームは別のFragmentです。わりと単純で構造としてはこんな感じなんですけど、もうこの時点で、すでにやばい香りがぷんぷんします。
さらに、Ver2.9.23から、女性向けにさっき下のほうに出ていたスタンプの機能が追加されました。ただし、条件付きで、送信先は男性だけです。送信先の男性に今まで一度もメールを送ったことがないときに表示されます。
見た目としては、スタンプのパレットをフォーム上に吹き出す感じで出して、入力フォームの切り替えボタンを押すとパレットが開いたり閉じたりする。あと、パレットにも閉じるボタンがあって、それを押すと、それまた閉じたり開いたりして、フォーム上にある切り替えボタンが赤色になったりします。
構造として、さらに赤字で表示しているところが追加されました。
まぁ、図にするとこんな感じで……。
もう地獄ですよね(笑)。
(会場笑)
さらに、年齢確認の有無によって、本文が読めなくなったり、メッセージの送信に制限があります。
地獄が生まれました。これをどうにかした話をします。
どうしてこんなことになったかという、そもそもの原因を話すと、典型的なFat Activityで、これを作った当時、ActivityとFragmentでロジックと状態を共有するベストな解決方法が思いつきませんでした。さらに、リファクタリングに取り組む時間が取れず、今の仕組みの上にどんどん機能を足していったという感じになっています。
このActivityとFragmentでロジックと状態をうまく共有する解決方法が出てきました。Android Architecture ComponentsのViewModelとLiveDataを組み合わせたらうまくいくんじゃないかということで、入れてみました。
軽く説明しておくと、ViewModelというのは、Activityが回転したときのデータ保持とか、複数のFragment間のデータ共有ができるやつです。LiveDataというのは、Lifecycleに従ってイイ感じにしてくれるデータホルダーです。LifecycleOwnerがSTARTEDまたはRESUMEDのときにアクティブになって通知してくれるやつですね。
LiveDataを使うとなにがいいかって、非同期処理でありがちな「処理が完了して、値を通知して、UIを操作しようとしたけど、肝心のUIは、例えば画面が閉じたりして非アクティブで、クラッシュしちゃう」みたいなことがないです。
「道具は揃った。さぁやるぞ」と思って、すぐに手をつけようってしたら、まぁだいたい失敗しますよね。だいたいリファクタリングが失敗する要因って、既存のコードを理解しないまま手を付けるとか、いきなり全部きれいにしようとするとか、カッとなって全部書き直すとかっていうのがあると思うんですけど、まずは手をつけるところをちゃんと決めましょう。
方針として、こんな感じで決めました。
まず、送信に関係する状態とロジックをViewModelに分割して分離する。プロフィールの取得とか、会話の履歴を取得するところともあったんですけど、これは送信の状態からはほぼ独立していたので、いったん後回しにしました。
送信状態で取りうる状態というのは画面全体の表示状態と関連が薄く、送信中は送信ボタンが消えてクルクルしたり、送信完了後スタンプパレットの表示状態が変わるぐらいでした。
ViewModelはステートマシンとして実装しました。これはなんでかというと、ほかの画面で実績があったからです。
そして、その状態を保持するホルダーはLiveDataを使いました。LiveDataを使った理由はとくになくて、正直なところ、ちょっと使ってみたかっただけです。ライフサイクルに正しくバインドできればRxを使ってもよいと思います。
ここがわりと重要なところで、新規のクラスのVMの周りはKotlinで、既存のクラスはJavaのまま我慢しました。さっきのリファクタリングが失敗する要因で、「いきなり全部書き直そうとする」というのは失敗する要因ですね。
では、この方針に従って順番にやっていきます。まず状態を落とし込んでいきます。
失敗したときの例外を一緒に持たせたいので、ステートマシンに使う状態というのはenumじゃなくてsealed classで定義しました。SendMailStateという名前でクラスを定義して、「まだなにもしていない」、「送信中」、「送信成功した」、「失敗した」。
ViewModelの実装というのはこんな感じです。
使うLiveDataは2つです。MutableLiveDataは、内部ではこいつにpostします。privateになっているのは、外から値を変更されたくないので公開はしないです。もう1つは公開用のLiveDataで、使う側はこいつをObserveするようにします。
これが正しいかどうかちょっとわかりませんが、初期値はinitでセットしています。
これが実際にメールを送るところです。
まずLiveDataが抱えている状態を一番上のif文でチェックして、送信中ならなにもしない。「getValue」メソッドで今抱えている値を取り出せるので、それを使っています。
送信中以外の場合はそのまま処理が進んで、今から送信するので「送信中」にセットします。そして、APIのレスポンスに応じてLiveDataに状態をセットしていきます。
このときに注意しないといけないのは、setValue、postValueというのがあるんですが、setValueのほうはUIスレッドだけです。IOスレッドで呼ぶと例外を起こします。なので、このコードではpostValueを使っています。
あとは、onCleared()というのは、これはViewModelが死ぬ時に呼び出されるやつです。
サブスクリプションをクリアするとか、そういう後始末に使います。
次に、ViewModel Factoryというのは、ViewModelのコンストラクタになにか引数を渡したいときに使うものです。ViewModelは直接newしてはいけません。なので、Factoryを定義してインスタンスを作ります。
では、実際にこのViewModelを使う側の話をしていきます。
まず、ActivityのonCreateでさっき定義したVMを生成します。そして、メッセージの入力フォームとスタンプパレットで、Activityの送信メソッドを呼んでいる箇所をVMの送信メソッドに置き換えます。VMが持っている送信状態をActivityと各FragmentでそれぞれObserveして、状態に応じてUIを変更する処理を実装します。
VMの初期化のコードはこんな感じです。
まず、ActivityのonCreateでFactoryを指定してVMを作ります。VMの状態を抱えているLiveDataがあるので、それでObserveしてハンドリングしていきます。このLiveDataはActivityがアクティブじゃないと通知してこないので、クラッシュしたりというのはないと思います。
これってメールの履歴画面はJavaで書かれているんですけど、if(~ instanceof ~)とかで気合でがんばっています。
あと、中で失敗したときは例外を取り出さないといけないんですが、それもいちいちキャストしてやったりします。なので、Javaにもwhen式とSmart Castほしいなってずっと思っています。
さらに、入力フォームとかスタンプパレット側でも同じことをします。Activityがオーナーになっている送信VMを取得します。Fragmentでも同じような感じでVMの状態をハンドリングします。
これは送信フォームの抜粋なんですが、送信フォームはJavaで書かれているので、when式とSmart Castを本当くれという気持ちでコードを書いていました。
2.9.23の構造はこんな感じになっていました。
リファクタリング後はこんな感じになりました。
すっきりした感があるんじゃないでしょうか?
さらに、実はこのあとちょっと作業を進めて、送信以外の部分についてもこれと同じような感じでリファクタリングを進めていきました。
プロフィールとメッセージの履歴取得の部分についても同じようにやりました。そうすると、Activityの行数は651から488に減りました。まだ多いですが、それでも多少すっきりしたと思います。
なんで減ったかというと、Activityが抱えていた入力フォームとかスタンプパレットの状態決定を、それぞれObserveして自分たちでやれという感じで移していったからです。まぁ減ったので、多少、機能の追加とか改修もやりやすくなったと思います。
これがリファクタリング後で、赤がメッセージ送信リクエストとその結果の通知です。
緑がプロフィールの取得と、あとやりとり履歴の取得の通知の結果です。
APIを通信をするようなところはほぼ全部ViewModel側に乗せることができました。あとはスタンプパレットの表示切り替えボタンを押したときとか、そういうのを追い出していけば、さらにActivityはダイエットできると思っていますが、まだやっていません。
では、まとめです。Android Architecture ComponentsのViewModelはいいぞ、と。Activityの回転とかFragmentのデータ共有に使える。なので、ぜひ積極的に使っていけば幸せになるかなと思います。
リファクタリングするときは、まず、いきなり手を付けずに、方針を決めましょう。ときには泥臭く地道にやっていくことも大事です。進めているうちにさらに良い解が見つかることがあるので、随時軌道修正しながらやっていくと、より良いものができると思います。
ちょっと早いですが、これで終わらせていただきます。ありがとうございました。
(会場拍手)
関連タグ:
2024.10.29
5〜10万円の低単価案件の受注をやめたら労働生産性が劇的に向上 相見積もり案件には提案書を出さないことで見えた“意外な効果”
2024.10.24
パワポ資料の「手戻り」が多すぎる問題の解消法 資料作成のプロが語る、修正の無限ループから抜け出す4つのコツ
2024.10.28
スキル重視の採用を続けた結果、早期離職が増え社員が1人に… 下半期の退職者ゼロを達成した「関係の質」向上の取り組み
2024.10.22
気づかぬうちに評価を下げる「ダメな口癖」3選 デキる人はやっている、上司の指摘に対する上手な返し方
2024.10.24
リスクを取らない人が多い日本は、むしろ稼ぐチャンス? 日本のGDP4位転落の今、個人に必要なマインドとは
2024.10.23
「初任給40万円時代」が、比較的早いうちにやってくる? これから淘汰される会社・生き残る会社の分かれ目
2024.10.23
「どうしてもあなたから買いたい」と言われる営業になるには 『無敗営業』著者が教える、納得感を高める商談の進め方
2024.10.28
“力を抜くこと”がリーダーにとって重要な理由 「人間の達人」タモリさんから学んだ自然体の大切さ
2024.10.29
「テスラの何がすごいのか」がわからない学生たち 起業率2年連続日本一の大学で「Appleのフレームワーク」を教えるわけ
2024.10.30
職場にいる「困った部下」への対処法 上司・部下間で生まれる“常識のズレ”を解消するには