Slide 1

Slide 1 text

Android アプリの マルチモジュール化と デモアプリの活用 こやまカニ大好き

Slide 2

Slide 2 text

自己紹介 こやまカニ大好き クックパッド モバイル基盤部 所属 モバイルアプリ開発の生産性を向上させ るタスクに従事 最近は WebView 大好き

Slide 3

Slide 3 text

今日のトピック ● マルチモジュール化の取り組み ● マルチモジュールプロジェクトの課題 ● デモアプリの紹介

Slide 4

Slide 4 text

クックパッドの マルチモジュール化の取り組み

Slide 5

Slide 5 text

クックパッドアプリの規模感 ● アプリ全体で 200 画面くらい(ダイアログも含む) ● Kotlin 2700 ファイル、 Java 600 ファイルくらい

Slide 6

Slide 6 text

マルチモジュール化の歴史 クックパッドアプリでは 2018 年からマルチモジュール化に取り組んでいる 2018年のイベントで初期のマルチモジュール化について発表 【開催レポ】Cookpad.apk #1 〜筋肉はすべてを解決する〜 https://techlife.cookpad.com/entry/2018/11/01/090059

Slide 7

Slide 7 text

2018 年時点での構想 左側が 2018 年時点での現状 :app と :legacy が分離しただけの状態 そこから :legacy をどんどん分解して右 側のようにモジュールを増やしていく想定 :legacy :app :legacy :app :featureA :featureB :ui :lib

Slide 8

Slide 8 text

マルチモジュール化の現状

Slide 9

Slide 9 text

マルチモジュール化の現状 ● 共通ビジネスロジックはモジュール化完了 ● 画面実装モジュールも16個存在している ● legacy モジュールは引き続き存在している ○ 1,2 年で全部無くせると思っていたが、作業が大変なものが残ってしまっている ● legacy モジュールの解体作業を進めるよりも 新しい機能を別モジュールで作りやすくするという部分に注力

Slide 10

Slide 10 text

● multi_module.md というファイルにモジュール構成や思想がまとめている ○ モジュールを増やしたり分割する作業で迷う事が減る ○ 「未定」とか「特殊」とか書かれていると課題感がわかるので良い ● モジュールの分割ポリシーはプロジェクト毎に異なるので、各プロジェクトの方針 をドキュメントに残すと良い マルチモジュールドキュメントのすすめ

Slide 11

Slide 11 text

マルチモジュール化の方針 以下の3種類に大まかに分類する ● アプリケーションモジュール ● 画面実装(VIPERシーン) モジュール ● ビジネスロジックモジュール

Slide 12

Slide 12 text

アプリケーションモジュール ビルド設定ごとに :app プレフィックスをつけたモジュールを定義する ● リリース用ビルド、開発用ビルドなどそれぞれモジュールで区別する ● APIサーバーの接続先設定などは各 :app モジュールが参照するモジュールを 切り替えることによって実現する(後述)

Slide 13

Slide 13 text

画面実装(VIPERシーン) モジュール 画面実装(VIPERシーン)は :feature プレフィックスをつけたモジュールで定義 ● :feature:tsukurepo, :feature:bookmark のような命名になる ● 他の :feature 系モジュールや :legacy への依存は許容しない ● 複数の画面(VIPERシーン)がモジュール内に含まれても良い ● Dynamic Feature モジュール ではない

Slide 14

Slide 14 text

ビジネスロジックモジュール ビジネスロジックは :library プレフィックスをつけたモジュールで定義 ● :library:infra, :library:ui のような命名になる ● 他のモジュールとやりとりする部分は interface / data class で定義 ● 実装クラスはできるだけ internal class で実装する

Slide 15

Slide 15 text

特殊なモジュール ● :setting:base ○ サーバの接続先設定などの interface だけが入ったモジュール ● :setting:internal ● :setting:external ○ :setting:base の開発版・本番実装 ○ :app モジュールでどちらかを選んで依存を追加する ● :feature:debug_menu ○ デバッグツール等の実装を押し込めたモジュール ○ 開発版アプリの :app モジュールだけが依存している

Slide 16

Slide 16 text

モジュール依存ツリー リリース版 開発版 :app:release :app:internal :feature: debug_menu :settings:external :settings:internal :settings:base :library modules :feature modules Hyperion, Flipper などの デバッグツールはここ 開発用サーバの設定 本番サーバの設定

Slide 17

Slide 17 text

マルチモジュール プロジェクトの課題

Slide 18

Slide 18 text

マルチモジュールプロジェクトの課題 ● :feature モジュールの差分を確認するのが面倒 ● 画面遷移の解決が難しい ● リソース名の重複で上書きされるのが辛い ● AndroidManifest の記述が分散する ● gradle の共通設定をどこに書けば良いのか問題 ● などなど

Slide 19

Slide 19 text

マルチモジュールプロジェクトの課題 ● :feature モジュールの差分を確認するのが面倒 ● 画面遷移の解決が難しい ● リソース名の重複で上書きされるのが辛い ● AndroidManifest の記述が分散する ● gradle の共通設定をどこに書けば良いのか問題 ● などなど

Slide 20

Slide 20 text

:feature モジュールの差分を 確認するのが面倒 ● 動作確認のためにアプリ全体をビルドする必要がある ○ 巨大な legacy モジュールが存在するクックパッドアプリでは特に辛い ● 画面確認のための確認手順が複雑な場合がある ○ ユーザーのログイン状態 ○ アプリの利用状態 ○ APIのレスポンス ○ などなど

Slide 21

Slide 21 text

デモアプリの活用

Slide 22

Slide 22 text

● 特定の :feature モジュールだけに依存する最小規模のアプリ ● :demo プレフィックスを持つアプリモジュールとして実装 デモアプリの概要

Slide 23

Slide 23 text

デモアプリの概要

Slide 24

Slide 24 text

デモアプリの様子 本来は「ログイン」 「レシピ選択」してからでな いと遷移できない

Slide 25

Slide 25 text

:feature:tsukurepo に同一の変更を加えて差分ビルド時間を計測した 開発版クックパッドアプリのビルド : 40秒 つくれぽ機能デモアプリのビルド : 12秒 デモアプリの効果

Slide 26

Slide 26 text

● :feature モジュールの各画面に遷移するためのUI ● APIレスポンスなどの Stub 実装 ● Stub 実装を Inject するための仕組み デモアプリの構造

Slide 27

Slide 27 text

RecyclerView にタイトルを列挙しているだけ 必要なパラメータを渡して画面遷移させる デモアプリのUI

Slide 28

Slide 28 text

APIレスポンスやユーザー状態を表現する Stub 実装 JSONファイルを読み取ってパースしたり、固定の object を返却したり デモアプリの Stub 実装

Slide 29

Slide 29 text

Stub 実装のクラスが本番クラスの代わりに Inject されるような仕組みが必要 (Dagger Hilt を使っている前提) デモアプリの Stub Injection

Slide 30

Slide 30 text

● ライブラリ側の Dagger Module では @InstallIn しない ● アプリ側で Dagger Module を include し、まとめて @InstallIn する ● デモアプリでは必要なコンポーネントを全て Stub する デモアプリの Stub Injection(旧)

Slide 31

Slide 31 text

デモアプリの Stub Injection(旧) :library :library :library :app 後から依存を差し替えるため、ここでは @InstallIn しない app モジュールで各ライブラリの Module を include

Slide 32

Slide 32 text

デモアプリの Stub Injection(旧) ライブラリ側の Dagger Module を include すると必要なコンポーネントだ け差し替えるということができないので、 feature モジュールに必要なコンポーネントの Stub を全て bind する :demo

Slide 33

Slide 33 text

デモアプリの Stub Injection(旧) の課題 ● デモアプリの Dagger Module 定義が肥大化しすぎる ● ライブラリモジュールを作る際に普通に @InstallIn してしまいがち ● 本番実装が internal class なのでデモアプリから参照できない ● 一部だけ本番実装を使うといったことができない ○ SharedPreferences に読み書きするだけのクラスはそのまま使いたい

Slide 34

Slide 34 text

デモアプリの Stub Injection(新) クックパッドアプリでは Fake Inject Layer と呼んでいる 以下のような機能を満たす新しい Stub Injection の仕組み ● Dagger Hilt の機能で実現する ● Stub が必要なコンポーネントだけを Stub と差し替えられる ● Stub と差し替えなかったコンポーネントは本番実装を利用する

Slide 35

Slide 35 text

デモアプリの Fake Inject Layer ● @DebugOverride という Qualifier を用意する ● Stub したいインターフェイスについて、 @DebugOverride @BindsOptionalOf の定義を追加する ○ これにより、@DebugOverride Optional の依存を Inject してくれるようになる

Slide 36

Slide 36 text

デモアプリの Fake Inject Layer ● 元の実装を bind していた箇所を以下のように変更する ○ @DebugOverride でインスタンスが提供されている場合はそれを使い、 提供されていない場合は本物の実装を利用する

Slide 37

Slide 37 text

デモアプリの Fake Inject Layer ● :demo アプリで差し替えたいインスタンスを @DebugOverride で提供する ○ ここでは Optional として定義する必要は無い

Slide 38

Slide 38 text

デモアプリの Fake Inject Layer :demo :feature :app :library Stub(@DebugOverride) があれば Stub を使う Stub がなければ本番実装を使う Fake Inject Layer を提供 :app では何もしない Stub が必要なコンポーネントだけ @DebugOverride で上書きできる 記述量がかなり少なくて済む

Slide 39

Slide 39 text

● @BindsOptionalOf の記述が必要になるため、ライブラリモジュールの Dagger 実装が増えた ○ 仕組みを理解していないと謎のおまじないにしか見えない ○ デモモジュールの実装量はかなり減った ● Fake Inject layer の処理が本番コードに入ってしまっている ○ これはかなり良くないので将来的には別の方法で解決したい ○ デモアプリの利便性を考えて現在のクックパッドアプリではこのようにしている Fake Inject layer の難点

Slide 40

Slide 40 text

まとめ ● マルチモジュールプロジェクトの動作確認にはデモアプリが便利 ○ 見た目の調整だけならかなりの部分はデモアプリで代用可能 ● デモアプリの依存解決は難しい ● デモアプリの依存解決は難しい…

Slide 41

Slide 41 text

モバイルエンジニア募集中 https://cookpad.com/ct/205307 未承諾広告※

Slide 42

Slide 42 text

おわり