Slide 1

Slide 1 text

Android Architecture Componentsを 使ってリファクタリングした話

Slide 2

Slide 2 text

● Yoshihisa Takeda ● Twitter: @bomneko_attack ● 株式会社Diverse ○ YYC Androidアプリ開発 ○ Androidデビューから1年経過 ● 光の戦士 ○ ララフェル♀ 白魔道士

Slide 3

Slide 3 text

メッセージ履歴画面 ● ユーザ間のメッセージのやりとりをチャット風に表示する ● ユーザの状態(性別/年齢確認(18歳以上かの確認)/これ までのやりとりの有無等)で表示が変わる ○ スクショは女性で年齢確認済みかつ相手の男性にこれまでメー ルを送ったことがない人

Slide 4

Slide 4 text

(旧)構造 ● Activity ○ メッセージ履歴を表示する RecyclerView + Adapter ○ ユーザのプロフィール取得 (写真とか性別とか) ○ メッセージ取得 ○ メッセージ送信 ● メッセージ入力フォームFragment ○ 各種のボタン(画像添付/スペシャルメール/テンプレート) ■ スペシャルメールボタンをタップすると初回は説明表示があるため Activityにコールバック ○ 入力フィールドと送信ボタン ■ バリデーション ■ 送信ボタンをタップすると Activityにコールバックされ送信される

Slide 5

Slide 5 text

(旧)構造

Slide 6

Slide 6 text

既にやばい香りがする

Slide 7

Slide 7 text

そこに… ● Ver2.9.23: 女性向け新機能 スタンプ送信機能追加 ● ただし条件付き ○ 送信先は男性のみ ○ 送信先の男性に一度もメールを送ったことがない ● UI ○ スタンプのパレットをフォームの上に吹き出す感じで ○ 入力フォームの切替ボタンを押すとパレットが開いたり閉じたり ○ パレットが開いている状態だと切替ボタンの色が赤色になる

Slide 8

Slide 8 text

構造 ● Activity ○ メッセージ履歴を表示するRecyclerView + Adapter ○ ユーザのプロフィール取得(写真とか性別とか) ○ メッセージ取得 ○ メッセージ送信 ● メッセージ入力フォームFragment ○ 各種のボタン(画像添付/スペシャルメール/テンプレート/スタンプパレット) ■ スペシャルメールボタンをタップすると初回は説明表示があるためActivityにコールバック ■ スタンプパレットボタンをタップするとパレットの表示切替のためActivityにコールバック ○ 入力フィールドと送信ボタン ■ バリデーション ■ 送信ボタンをタップするとActivityにコールバックされ送信される ● スタンプパレットFragment ○ 送信できるスタンプをサーバから取得 ○ スタンプをタップするとActivityにコールバックされ送信される

Slide 9

Slide 9 text

ver2.9.23構造

Slide 10

Slide 10 text

他にも… ● 年齢確認の有無による表示切替 ○ メッセージ本文が読めない (ブラーがかかる) ○ メッセージの送信に制限がある

Slide 11

Slide 11 text

地獄爆誕

Slide 12

Slide 12 text

どうしてこんなことに… ● 典型的なFat Activity ● (当時)ActivityとFragmentでロジックと状態を共有するベストな解決策が思いつかな かった ● リファクタリングに取り組む時間がなくこの仕組みの上に機能改修を行っていった

Slide 13

Slide 13 text

どうしてこんなことに… ● 典型的なFat Activity ● (当時)ActivityとFragmentでロジックと状態を共有するベストな解決策が思いつかな かった ● リファクタリングに取り組む時間がなくこの仕組みの上に機能改修を行っていった Android Architecture Components View Model + LiveData

Slide 14

Slide 14 text

ViewModel & LiveData ● ViewModel(以下, VM) ○ Activityが回転したときのデータ保持 ○ 複数のフラグメント間とのデータ共有 ● LiveData ○ Lifecycleに従っていい感じにしてくれるデータホルダー ○ Lifecycle OwnerがSTARTEDまたはRESUMEDのときのみアクティブになり値を通知してくれる ■ 非同期処理でありがちな「処理が完了して値を通知し UIを操作しようとしたけれど非アクティ ブになっていてクラッシュ」がない

Slide 15

Slide 15 text

さぁやるぞ! ● リファクタリングが失敗する要因 ○ 既存のコードを理解しないまま手を付ける ○ いきなり全部キレイにしようとする ○ カッとなって全部書き直そうとする ● ひとまず手を付けるところを決めよう

Slide 16

Slide 16 text

方針 ● まず送信に関係する状態とロジックをViewModelに分離することを目指す ○ プロフィール取得・履歴取得とは独立していた ○ 送信状態で取り得る状態は画面全体の表示状態と関連が薄かった ■ 送信中は送信ボタンが消えて代わりにプログレスが表示される ■ 送信完了後スタンプパレットとスタンプパレット表示切り替えボタンを非表示にして履歴をリ ロードする ● VMはステートマシンとして実装する ○ 他の画面で実績があった ○ 状態を保持するホルダーに LiveDataを使う ■ (正直なところ)使ってみたかっただけ ■ ライフサイクルに正しくバインドできれば Rxでも良い ● 新規クラスのVM周りはKotlinで、既存クラスはJavaのまま我慢

Slide 17

Slide 17 text

状態の落とし込み 失敗したときに例外を一緒に持たせたいので enum classではなくsealed classで定義する

Slide 18

Slide 18 text

ViewModelの実装 ● 使うLiveDataは2つ ○ MutableLiveData: 内部ではコイツに値を postする. 外から値を変更されたくないので公開しない ○ LiveData: 外部公開用. 使う側はコイツをObserveする ○ 初期値はコンストラクタでセットしておく

Slide 19

Slide 19 text

● LiveDataが抱えている状態が「送信中」なら何もしない ○ 「getValue」メソッドで今抱えている値を取り出せる ● 送信中以外の場合は状態を「送信中」にセット ● APIのレスポンスに応じて LiveDataに状態をセット ○ setValue: UIスレッドのみ. IOスレッドで呼ぶと例外 ○ postValue: IOスレッドでも使える ○ このコードではIOスレッドで状態をセットするので「 postValue」

Slide 20

Slide 20 text

● ViewModelが死ぬときに呼び出される ● サブスクリプションをクリアする等後始末をするのに使う

Slide 21

Slide 21 text

ViewModel Factory ● ViewModelのコンストラクタに引数を渡すときに使う ○ ViewModelは直接newしてはいけない

Slide 22

Slide 22 text

処理の移動と状態遷移の追跡 ● ActivityのonCreateでVMを生成 ● メッセージ入力フォームとスタンプパレットでActivityの送信メソッドを読んでいる箇 所をVMの送信メソッドを呼ぶように変更 ● VMがもっている送信状態をActivityと各FragmentのそれぞれでObserveして状態 に応じてUI等を変更する処理を実装する

Slide 23

Slide 23 text

VMの初期化 ● ActivityのonCreateでFactoryを指定してVMを生成 ● VMの状態を抱えているLiveDataをObserveしてハンドリング ○ Activityがアクティブでないと通知してこない ● Javaにもwhen式とスマートキャストをくれ

Slide 24

Slide 24 text

FragmentでVMを使う ● 親Activityがオーナーになっている送信 VMを取得する ● FragmentでもVMの状態をハンドリングする ● Javaにもwhen式とスマートキャストをくれ(大事なことなので(ry)

Slide 25

Slide 25 text

ver2.9.23構造

Slide 26

Slide 26 text

リファクタ後の構造

Slide 27

Slide 27 text

リファクタリングの結果 ● 送信に関する責務をVMに追い出し ○ 各FragmentはVMが抱えている状態を監視して表示などを変更するように (多少は)キレイになった!!!!

Slide 28

Slide 28 text

送信機能以外の部分について ● プロフィール・メッセージ履歴取得部分についても 同様のアプローチでリファクタリングした ○ プロフィールとメッセージ履歴は画面表示の状態に密に関連していたので 1つのVM ● Activityの行数: 651 ー> 488 ○ Activityが抱えていたInput Form FragmentやStamp Palette Fragmentの状態決定を それぞれのFragmentで行うように ○ 機能の追加・改修もある程度しやすくなった (はず)

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

まとめ ● Android Architecture Components ViewModelはいいぞ ○ Activityの回転時のデータ保持 ○ Fragment間のデータ共有 ● リファクタリングするときは方針を決めよう ○ ときには泥臭く地道にやっていきも大事 ○ 進めているうちにさらに良い解が見つかることもあるので随時軌道修正

Slide 31

Slide 31 text

参考資料 ● Architecture Components https://developer.android.com/topic/libraries/architecture/ ○ ViewModel https://developer.android.com/topic/libraries/architecture/viewmodel ○ LiveData https://developer.android.com/topic/libraries/architecture/livedata