Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ZOZOTOWN AndroidへのJetpack Composeの導入 / Introduc...

ZOZOTOWN AndroidへのJetpack Composeの導入 / Introducing Jetpack Compose for ZOZOTOWN Android

イベントURL
https://pepabo.connpass.com/event/219431/

カジュアル面談応募フォーム
https://hrmos.co/pages/zozotech/jobs/0000193

KeitaTakahashi

August 05, 2021
Tweet

More Decks by KeitaTakahashi

Other Decks in Technology

Transcript

  1. ZOZOTOWN AndroidへのJetpack Composeの導入
 2021/08/05(Thu)
 Android Meetup【ZOZOテクノロジーズ × サイバーエージェント × GMOペパボ】


    株式会社ZOZOテクノロジーズ
 ZOZOTOWN本部 ZOZOアプリ部 Androidチーム
 高橋 啓太
 
 Copyright © ZOZO Technologies, Inc.
  2. © ZOZO Technologies, Inc. https://zozo.jp/
 3 • 日本最大級のファッション通販サイト
 • 1,400以上のショップ、8,200以上のブランドの取り扱い(ともに2021年3月

    末時点)
 • 常時83万点以上の商品アイテム数と毎日平均2,900点以上の新着 商品 を掲載
 • コスメ専門モール「ZOZOCOSME」や靴の専門モール
 「ZOZOSHOES」、ラグジュアリー&デザイナーズゾーン
 「ZOZOVILLA」を展開
 • 即日配送サービス
 • ギフトラッピングサービス
 • ツケ払い など

  3. © ZOZO Technologies, Inc. 5 Jetpack Composeとは • 宣言的UIのツールキット ◦

    Thinking in Compose | Jetpack Compose • Kotlinのコード上でUIの実装が可能になる • XMLを用いた既存のレイアウトと相互運用可能 • 1.0.0(stable)がリリースされました🎉
  4. © ZOZO Technologies, Inc. 9 導入のステップ 1. 開発環境の整備 ◦ Android

    Studio Arctic Foxでの既存プロジェクトのビルド ◦ ライブラリ等のアップデート 2. 課題の洗い出し 3. 課題の解決
  5. © ZOZO Technologies, Inc. 10 Android Studio Arctic Foxでの既存プロジェクトのビルド •

    Android Studio Arctic FoxではJetpack Composeの開発がサポートされた ◦ Compose UIのプレビューなど • AGP 7.0.0 & JDK 11を導入 ◦ Arctic Fox Canary 9からAGP 7.0.0を使用する場合にJDK 11が求められるように なった ◦ AGP 7.0: Next major release for the Android Gradle plugin • Hiltのアップデート • ZOZOTOWN Androidをビルドし、問題なくビルドできることを確認
  6. © ZOZO Technologies, Inc. 11 ライブラリ等のアップデート • Jetpack Compose beta08からはKotlin

    1.5以上が必要となるためアップデートを実施 ◦ 各種ドキュメントの確認と影響箇所の調査 ▪ ドキュメントの内容をチームで読み合わせ ▪ Compatibility guide for Kotlin 1.5 ▪ Kotlin releases • androidx.lifecycleのアップデート ◦ Composable内でflowWithLifecycleを利用するため ◦ Lifecycle
  7. © ZOZO Technologies, Inc. 14 課題の洗い出し プロトタイプ実装とレビューによって明らかになった課題 • 既存のZOZOTOWN AndroidのUIの状態管理方法がComposeに適していない

    ◦ UIの状態管理・Eventの処理方法を見直す必要があった • 無秩序なComposable作成によるComposableの再利用性・可読性低下 ◦ チームでの開発に向けてComposableの設計ルールを制定する必要があった
  8. © ZOZO Technologies, Inc. 15 導入のステップ 1. 開発環境の整備 2. 課題の洗い出し

    3. 課題の解決 ◦ UIの状態管理・Eventの処理方法の見直し ◦ Composable設計ルールの制定
  9. © ZOZO Technologies, Inc. 18 ComposeのUI更新 “Compose は常に、UI のうち再描画する必要がある部分をインテリジェントに選択します。” Compose

    の思想 | Jetpack Compose → 差分を意識してデータを通知する必要がない 更新差分のあるViewDataのみを保持している
  10. © ZOZO Technologies, Inc. 21 Fragment ViewModel 更新差分を通知 View View

    アプリケーションの 状態更新 UI更新の流れ(既存のZOZOTOWN Android)
  11. © ZOZO Technologies, Inc. 22 Fragment ViewModel 更新差分を通知 View View

    更新 アプリケーションの 状態更新 UI更新の流れ(既存のZOZOTOWN Android)
  12. © ZOZO Technologies, Inc. 23 Fragment ViewModel 更新差分を通知 View View

    更新 Composeには適さないデー タ構造 アプリケーションの 状態更新 UI更新の流れ(既存のZOZOTOWN Android)
  13. © ZOZO Technologies, Inc. 24 UIの状態管理(既存のZOZOTOWN Android) ViewData data class

    ItemViewData( val name: String, val price: String, ) • UIに表示するデータをまとめたdata class • カスタムビュー単位で作成
  14. © ZOZO Technologies, Inc. 25 ViewState sealed class ViewState {

    object Initialized(val shopviewData: HogeViewData) : ViewState() data class Favorited(val itemViewData: ItemViewData) : ViewState() } • UIの状態を表すsealed class • 画面単位で作成 • ViewDataを保持する • ViewStateの切り方についての明確なルールは存在しない UIの状態管理(既存のZOZOTOWN Android)
  15. © ZOZO Technologies, Inc. 26 差分のあるデータのみを通知 Fragment/ActivityへViewStateを通知するLiveData/Flow class HogeViewModel :

    ViewModel() { private val _viewState = MutableStateFlow<ViewState>() val viewState: StateFlow<ViewState> = _viewState ... private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } UIの状態管理(既存のZOZOTOWN Android)
  16. © ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private

    val _viewState = MutableStateFlow<ViewState>() val viewState: StateFlow<ViewState> = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 27 Activity/FragmentにViewStateを通知するFlow/LiveData UIの状態管理(既存のZOZOTOWN Android)
  17. © ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private

    val _viewState = MutableStateFlow<ViewState>() val viewState: StateFlow<ViewState> = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 28 データの更新とFlow/LiveDataにViewStateを流すメソッド UIの状態管理(既存のZOZOTOWN Android)
  18. © ZOZO Technologies, Inc. 29 class HogeViewModel : ViewModel() {

    private val _viewState = MutableStateFlow<ViewState>() val viewState: StateFlow<ViewState> = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } UIの状態管理(既存のZOZOTOWN Android)
  19. © ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private

    val _viewState = MutableStateFlow<ViewState>() val viewState: StateFlow<ViewState> = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 30 更新差分のあるViewDataのみを保持している UIの状態管理(既存のZOZOTOWN Android)
  20. © ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private

    val _viewState = MutableStateFlow<ViewState>() val viewState: StateFlow<ViewState> = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 31 更新差分のあるViewDataのみを保持している Composeでは扱い辛い UIの状態管理(既存のZOZOTOWN Android)
  21. © ZOZO Technologies, Inc. 32 ComposeのUI更新 “Compose は常に、UI のうち再描画する必要がある部分をインテリジェントに選択します。” Compose

    の思想 | Jetpack Compose → 差分を意識してデータを通知する必要がない 更新差分のあるViewDataのみを保持している
  22. © ZOZO Technologies, Inc. 33 ComposeのUI更新 “Compose は常に、UI のうち再描画する必要がある部分をインテリジェントに選択します。” Compose

    の思想 | Jetpack Compose → 差分を意識してデータを通知する必要がない  → これを活用するためには画面全体の状態を一つにまとめる必要がある 更新差分のあるViewDataのみを保持している
  23. © ZOZO Technologies, Inc. 36 参考実装の調査 参考となるプロジェクトの実装調査: tivi ◦ Jetpack

    Composeで実装されたプロジェクト ◦ 既存のZOZOTOWNと近いアーキテクチャ ◦ chrisbanes/tivi
  24. © ZOZO Technologies, Inc. 37 UIの状態管理方法の見直し 更新差分のあるViewDataのみを保持している data class HogeViewState(

    val itemViewData: ItemViewData, val shopViewData: ShopViewData, ) 1. 画面全体のViewDataを統合 画面すべてのViewDataを持つViewStateを作成
  25. © ZOZO Technologies, Inc. 38 UIの状態管理方法の見直し 差分のあるデータのみを通知 2. 通知方法の見直し private

    val itemState = MutableStateFlow(ItemViewData.Empty) private val shopState = MutableStateFlow(ShopViewData.Empty) val viewState = combine(itemState, shopState) { item, shop -> HogeViewState( item, shop, ) } アプリケーションやViewの状態をそれぞれFlowで管理
  26. © ZOZO Technologies, Inc. 39 UIの状態管理方法の見直し 差分のあるデータのみを通知 アプリケーションやViewの状態をそれぞれFlowで管理 → 監視しているFlowの変更時に、画面全体のViewStateを生成して通知する

    2. 通知方法の見直し private val itemState = MutableStateFlow(ItemViewData.Empty) private val shopState = MutableStateFlow(ShopViewData.Empty) val viewState = combine(itemState, shopState) { item, shop -> HogeViewState( item, shop, ) }
  27. © ZOZO Technologies, Inc. 40 UIの状態管理方法の見直し 差分のあるデータのみを通知 Composableでは常に画面全体の状態(ViewState)を受け取る 3. ComposableでのViewStateの監視

    val viewState by remember(viewModel.viewState, lifecycleOwner) { viewModel.viewState.flowWithLifecycle( lifecycleOwner.lifecycle, Lifecycle.State.STARTED, ) }.collectAsState(HogeViewState.Empty)
  28. © ZOZO Technologies, Inc. 41 UIの状態管理方法の見直し Composable Composable Composable Composable

    ViewModel Flow<ViewState> State ViewState 画面全体の状態を通知
  29. © ZOZO Technologies, Inc. 43 Eventの処理方法 ViewEvent sealed class ViewEvent

    { object ClickItem : ViewEvent() data class ClickShop(val id: Int) : ViewEvent() } ユーザーインタラクションなどによって発生するイベントを定義したsealed class
  30. © ZOZO Technologies, Inc. 44 Eventの処理方法 class HogeViewModel : ViewModel()

    { private val _viewEvent = MutableSharedFlow<ViewEvent>() fun dispatchViewEvent(event: ViewEvent) { viewModelScope.launch { _viewEvent.emit(event) } } } Flow/LiveData(SingleLiveEvent)でViewEventを通知
  31. © ZOZO Technologies, Inc. 45 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel)

    { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } }
  32. © ZOZO Technologies, Inc. 46 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel)

    { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } } 各画面のトップレベルのComposableからViewEventを発行
  33. © ZOZO Technologies, Inc. 47 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel)

    { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } } 下層のComposableからはViewEventを意識しない
  34. © ZOZO Technologies, Inc. 48 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel)

    { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } } 下層のComposableからはViewEventを意識しない Composableの再利用性が上がる
  35. © ZOZO Technologies, Inc. 49 Composable Composable Composable Composable ViewModel

    Event発生 ViewEventの発行からUIの更新まで
  36. © ZOZO Technologies, Inc. 50 Composable Composable Composable Composable ViewModel

    Event発生 ViewEventの発行からUIの更新まで
  37. © ZOZO Technologies, Inc. 51 Composable Composable Composable Composable ViewModel

    Event発生 ViewEventの発行からUIの更新まで
  38. © ZOZO Technologies, Inc. 52 Composable Composable Composable Composable ViewModel

    Event発生 ViewEventを発行 ViewEventの発行からUIの更新まで
  39. © ZOZO Technologies, Inc. 53 Composable Composable Composable Composable ViewModel

    Event発生 Stateを更新 ViewEventの発行からUIの更新まで State
  40. © ZOZO Technologies, Inc. 56 ViewEventの発行からUIの更新まで Composable Composable Composable Composable

    ViewModel State ViewState 表示するデータ 差分更新 差分更新 表示するデータ
  41. © ZOZO Technologies, Inc. 57 ZOZOTOWNへのCompose導入の課題 ✅ 既存のZOZOTOWNのUIの状態管理方法がComposeに適していない ◦ UIの状態管理・Eventの処理方法を見直す必要があった

    • 無秩序なComposable作成によるComposableの再利用性・可読性低下 ◦ チームでの開発に向けてComposableの設計ルールを制定する必要があった
  42. © ZOZO Technologies, Inc. 58 Composable設計ルールの制定 無秩序なComposable作成によるComposableの再利用性・可読性低下 • Reactなど、Webフロントの宣言的UIフレームワークでの事例を調査 ◦

    Atomic Designを使ってReactコンポーネントを再設計した話, スペースマーケットブログ ◦ Atomic Design ベースの Vue コンポーネント設計, Qiita • チームでの開発に備え、Composableの設計ルールを検討
  43. © ZOZO Technologies, Inc. 59 AtomicDesign • 主にWebフロントの世界で用いられるデザインシステム ◦ Amazon.co.jp:

    Atomic Design ~堅牢で使いやすいUIを効率良く設計する : 五藤 佑典: Japanese Books • 堅牢性の高いUI実装を実現することができる • UIの要素(コンポーネント)をAtoms, Molecules, Organisms, Templates, Pagesに分類し、 階層化する Atomic Design | Brad Frost
  44. © ZOZO Technologies, Inc. 61 AtomicDesign Pages Templates Organisms Molecules

    Atoms 下層のコンポーネントは上層のコンポーネントに依存不可
  45. © ZOZO Technologies, Inc. 62 Atoms 機能的に分割できる最小のUI要素 • ZOZOTOWN Androidでは

    ◦ Text, Button, Row, ColumnなどComposeで提供されて いるComposable ◦ 吹き出しやアイコンなど、独自に作成したComposableの うちそれ以上分割できないもの • 命名規則 ◦ すべてのアプリケーション利用できるように、可能な限り抽 象的な名前とする
  46. © ZOZO Technologies, Inc. 63 Molecules ユーザーに対して目的・動機を提供する最小のUI要素 • ZOZOTOWN Androidでは

    ◦ TextやRowなどのAtomsを組み合わせて作成した Composable • 命名規則 ◦ ユーザーが何を実現したいのかを表す名前とする
  47. © ZOZO Technologies, Inc. 64 Organisms 独立して成立するコンテンツ • ZOZOTOWN Androidでは

    ◦ Atoms, Molecules, Organismsを組み合わせて作成 したComposable • 命名規則 ◦ 情報自体を表す名前とする
  48. © ZOZO Technologies, Inc. 65 Templates ページのコンテンツ構造を明確にするオブジェクト • ZOZOTOWN Androidでは

    ◦ 画面全体のレイアウトを管理し、各UI要素に必要な ViewDataを分配する ◦ 1画面に対して1つ作成する • 命名規則 ◦ [画面名]Screen
  49. © ZOZO Technologies, Inc. 66 Pages 実際のデータを画面に反映するオブジェクト • ZOZOTOWN Androidでは

    ◦ ViewModelへの依存(動的なデータへの関心)を持つ ◦ ViewModelにViewEventを発行する ◦ Templatesに実際のデータを提供する • 命名規則 ◦ [画面名]Screen
  50. © ZOZO Technologies, Inc. 67 ZOZOTOWN Androidでの例(デバッグメニュー) Atoms Molecules Organisms

    Text, Button AccessPointSpinner AccessPoint Templates, Pages DebugMenuScreen
  51. © ZOZO Technologies, Inc. 68 ZOZOTOWN AndroidへのCompose導入の課題 ✅ 既存のZOZOTOWNのUIの状態管理方法がComposeに適していない ◦

    UIの状態管理・Eventの処理方法を見直す必要があった ✅ 無秩序なComposable作成によるComposableの再利用性・可読性低下 ◦ チームでの開発に向けてComposableの設計ルールを制定する必要があった
  52. © ZOZO Technologies, Inc. 70 まとめ • ZOZOTOWN AndroidへJetpack Composeを導入した

    • Compose導入のための課題を洗い出し、解決した ◦ UIの状態管理・Eventの処理方法を見直した ◦ AtomicDesignをベースにしたComposable設計ルールを制定した
  53. © ZOZO Technologies, Inc. 71 今後の課題 • UIの状態管理方法のルール化 ◦ チームでスムーズに開発を行うため、今回の検討内容をルールに落とし込む

    • より複雑な画面でのStateの扱い ◦ 今回検討した設計ではカバーできないユースケースへの対応 • Composable設計ルールのブラッシュアップ ◦ 実際にチームで運用し、よりZOZOTOWNに適した形にする • デザイナー・エンジニア間のユビキタス言語としてのAtomicDesignの活用 ◦ デザインに関するコミュニケーションをよりスムーズにする
  54. © ZOZO Technologies, Inc. 72 今後の課題 → 実装・検証を繰り返して改善する • UIの状態管理方法のルール化

    ◦ チームでスムーズに開発を行うため、今回の検討内容をルールに落とし込む • より複雑な画面でのStateの扱い ◦ 今回検討した設計ではカバーできないユースケースへの対応 • Composable設計ルールのブラッシュアップ ◦ 実際にチームで運用し、よりZOZOTOWNに適した形にする • デザイナー・エンジニア間のユビキタス言語としてのAtomicDesignの活用 ◦ デザインに関するコミュニケーションをよりスムーズにする