Slide 1

Slide 1 text

既存画面の
 Jetpack Composeでの書き換え:
 FAANSでの事例紹介
 2022/5/23 
 ZOZO Tech Talk #7 - Android
 株式会社ZOZO
 ブランドソリューション開発本部 FAANS部 フロントエンド
 テックリード
 堀江 亮介 
 Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

© ZOZO, Inc. 株式会社ZOZO
 ブランドソリューション開発本部 FAANS部 フロントエンド
 テックリード 堀江 亮介
 ● 自動化とビールが好き ● 最近は家族でドライブすることが多い ● @Horie1024 
 2

Slide 3

Slide 3 text

© ZOZO, Inc. 3 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 4

Slide 4 text

© ZOZO, Inc. 4 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 5

Slide 5 text

© ZOZO, Inc. 5 2021年3月期 決算説明会補足資料 - 株式会社ZOZO, https://d31ex0fa3i203z.cloudfront.net/assets/ja/ir/pdf/webup_fy20204q_j.pdf

Slide 6

Slide 6 text

© ZOZO, Inc. 6 2021年3月期 決算説明会補足資料 - 株式会社ZOZO, https://d31ex0fa3i203z.cloudfront.net/assets/ja/ir/pdf/webup_fy20204q_j.pdf

Slide 7

Slide 7 text

© ZOZO, Inc. 7 2021年3月期 決算説明会補足資料 - 株式会社ZOZO, https://d31ex0fa3i203z.cloudfront.net/assets/ja/ir/pdf/webup_fy20204q_j.pdf

Slide 8

Slide 8 text

© ZOZO, Inc. 8 ● Fashion Advisors are Neighbors略
 ● 「ショップスタッフの
 効率的な販売をサポートする
 ショップスタッフ専用ツール」
 ● Web, iOS, Androidで提供
 FAANSとは
 プレスリリース: ZOZOTOWNとブランド実店舗をつなぐOMOプラットフォーム「ZOZOMO」始動 - 株式会社ZOZO, https://corp.zozo.com/news/20211028-16352/

Slide 9

Slide 9 text

© ZOZO, Inc. 9 OMOプラットフォーム「ZOZOMO」
 2022年3月期 第2四半期 決算説明会補足資料 - 株式会社ZOZO, https://d31ex0fa3i203z.cloudfront.net/assets/ja/ir/pdf/J_webup_FY2021_2Q.pdf

Slide 10

Slide 10 text

© ZOZO, Inc. 10 スタッフコーデの連携
 プレスリリース: ZOZOTOWNとブランド実店舗をつなぐOMOプラットフォーム「ZOZOMO」始動 - 株式会社ZOZO, https://corp.zozo.com/news/20211028-16352/

Slide 11

Slide 11 text

© ZOZO, Inc. 11 実店舗在庫の連携
 2022年3月期 第2四半期 決算説明会補足資料 - 株式会社ZOZO, https://d31ex0fa3i203z.cloudfront.net/assets/ja/ir/pdf/J_webup_FY2021_2Q.pdf

Slide 12

Slide 12 text

© ZOZO, Inc. 12 実店舗在庫の連携
 2022年3月期 決算説明会補足資料 - 株式会社ZOZO, https://corp.zozo.com/wp-content/uploads/2022/04/JP_22034Q.pdf

Slide 13

Slide 13 text

© ZOZO, Inc. 13 実店舗在庫の連携
 2022年3月期 決算説明会補足資料 - 株式会社ZOZO, https://corp.zozo.com/wp-content/uploads/2022/04/JP_22034Q.pdf

Slide 14

Slide 14 text

© ZOZO, Inc. 14 実店舗在庫の連携
 2022年3月期 決算説明会補足資料 - 株式会社ZOZO, https://corp.zozo.com/wp-content/uploads/2022/04/JP_22034Q.pdf

Slide 15

Slide 15 text

© ZOZO, Inc. 15 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 16

Slide 16 text

© ZOZO, Inc. 16 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 17

Slide 17 text

© ZOZO, Inc. 17 FAANSの開発チーム
 Miroを見る

Slide 18

Slide 18 text

© ZOZO, Inc. 18 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 19

Slide 19 text

© ZOZO, Inc. 19 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 20

Slide 20 text

© ZOZO, Inc. 20 FAANS Androidの技術スタック
 Miroを見る

Slide 21

Slide 21 text

© ZOZO, Inc. 21 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 22

Slide 22 text

© ZOZO, Inc. 22 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 23

Slide 23 text

© ZOZO, Inc. 23 ● FAANSでは去年の8月に導入
 ● フルComposeではない
 ○ アプリの基本構成はSingle Activity + Navigation Component
 ○ ViewベースなUIとComposeで作られたUIが混在 
 Jetpack Compose使ってますか?


Slide 24

Slide 24 text

© ZOZO, Inc. 24 ● ZOZOでの導入状況
 ○ ZOZOTOWNでは導入済み
 ○ WEARは近日中
 Jetpack Compose使ってますか?
 ZOZOTOWN AndroidへのJetpack Compose導入の取り組み - ZOZO TECH BLOG, https://techblog.zozo.com/entry/zozotown-android-jetpack-compose

Slide 25

Slide 25 text

© ZOZO, Inc. 25 ● 開発効率が向上
 ○ 直感的な宣言型API
 ○ シンプルなコードでのUIの記述が可能
 例: UIの出し分け、リスト表示
 ○ 既存のViewベースなUIとの相互運用が容易
 ○ ドキュメントやサンプルコードが豊富
 ● 一部の既存ViewベースUIと相性が悪い
 ○ 例: BottomSheetDialogFragment
 Jetpack Composeを導入してどうだったか?


Slide 26

Slide 26 text

© ZOZO, Inc. 26 ● FAANSではViewベースなUIとComposeで作られたUIが混在 
 ● 今後のUI実装方針をどうするか?
 ○ UIは基本的にComposeで開発する方針に統一
 
 FAANSでのUI実装方針


Slide 27

Slide 27 text

© ZOZO, Inc. 27 ● 画面全体を一度に書き換えるのは難しい
 ○ リソース、タスク優先度、開発期間 etc…
 ● 相互運用APIの存在
 ○ 既存ViewベースUIとComposeを組み合わせて実装可能
 ○ 既存画面のUI要素を1つずつComposeへ移行可能
 ○ https://developer.android.com/jetpack/compose/interop
 ● 既存画面を段階的にComposeに書き換えていく
 既存画面のJetpack Composeでの書き換え


Slide 28

Slide 28 text

© ZOZO, Inc. 28 書き替え事例: コーディネート詳細


Slide 29

Slide 29 text

© ZOZO, Inc. 29 1. データの流れの整理
 2. UI要素のComposeへの書き換え
 3. 画面全体をComposeへ書き換え
 
 書き換えの流れ
 


Slide 30

Slide 30 text

© ZOZO, Inc. 30 ● UI Stateに画面の描画に必要な情報をまとめる
 ● UIの状態公開は1箇所に制限
 例. 複数のLiveDataの公開は避ける
 ● 単方向データフロー(UDF)に沿わせる
 1.データの流れの整理
 
 Android Developers アプリ アーキテクチャ ガイド UIレイヤ, https://developer.android.com/jetpack/guide/ui-layer

Slide 31

Slide 31 text

© ZOZO, Inc. 31 ● 画面を複数の要素に分解
 ● 要素をComposeで再実装し置換
 ● 要素単位でCustomViewを作成
 CustomViewでComposeの実装をラップ
 
 2.UI要素のComposeへの書き換え
 


Slide 32

Slide 32 text

© ZOZO, Inc. 32 ● layout.xmlを削除
 ● Composableのみで画面を構成
 
 3.画面全体をComposeへ書き換え
 


Slide 33

Slide 33 text

© ZOZO, Inc. 33 1. データの流れの整理
 2. UI要素のComposeへの書き換え
 3. 画面全体をComposeへ書き換え
 
 書き換えの流れ
 
 ● 2を複数回繰り返し、段階的にComposeへ移行
 ● 最終的に画面全体をComposeへ移行


Slide 34

Slide 34 text

© ZOZO, Inc. 34 ● 複数のLiveDataが公開
 ○ 状態の公開は1つに制限
 ○ データは1つのデータクラスにまとめる
 
 コーディネート詳細: データの流れの整理
 
 @HiltViewModel class CoordinateDetailDelegateImpl @Inject constructor( private val faansApiRepository: FaansApiRepository ) : ViewModel(), CoordinateDetailDelegate { private val _coordinate = MutableLiveData() override val coordinate: LiveData get() = _coordinate private val _navigateToEditCoordinate = MutableLiveData>() override val navigateToEditCoordinate : LiveData> get() = _navigateToEditCoordinate private val _isLoading = MutableLiveData() override val isLoading: LiveData get() = _isLoading private val _hasError = MutableLiveData() override val hasError: LiveData get() = _hasError

Slide 35

Slide 35 text

© ZOZO, Inc. 35 ● 複数のLiveDataが公開
 ○ 状態の公開は1つに制限
 ○ データは1つのデータクラスにまとめる
 ○ Eventクラスでラップした値(1度だけ処理したい)
 ■ 状態とは別に公開
 
 コーディネート詳細: データの流れの整理
 
 private val _navigateToEditCoordinate = MutableLiveData>() override val navigateToEditCoordinate: LiveData> get() = _navigateToEditCoordinate

Slide 36

Slide 36 text

© ZOZO, Inc. 36 private val _state = MutableStateFlow(State.Initial) val state: StateFlow = _state private val _event = MutableSharedFlow() val event: SharedFlow = _event data class State( val isLoading: Boolean = false, val coordinate: CoordinateDetail? = null, val coordinateReviewComment: String? = null, val totalViewCount: Long = 0, val totalSalesAmount: Long = 0, val coordinateItems: List = listOf(), ) { companion object { val Initial = State(isLoading = true) } } sealed interface Event { data class OnTransitionToEditPage(...) : Event }

Slide 37

Slide 37 text

© ZOZO, Inc. 37 private val _state = MutableStateFlow(State.Initial) val state: StateFlow = _state private val _event = MutableSharedFlow() val event: SharedFlow = _event data class State( val isLoading: Boolean = false, val coordinate: CoordinateDetail? = null, val coordinateReviewComment: String? = null, val totalViewCount: Long = 0, val totalSalesAmount: Long = 0, val coordinateItems: List = listOf(), ) { companion object { val Initial = State(isLoading = true) } } sealed interface Event { data class OnTransitionToEditPage(...) : Event }

Slide 38

Slide 38 text

© ZOZO, Inc. 38 private val _state = MutableStateFlow(State.Initial) val state: StateFlow = _state private val _event = MutableSharedFlow() val event: SharedFlow = _event data class State( val isLoading: Boolean = false, val coordinate: CoordinateDetail? = null, val coordinateReviewComment: String? = null, val totalViewCount: Long = 0, val totalSalesAmount: Long = 0, val coordinateItems: List = listOf(), ) { companion object { val Initial = State(isLoading = true) } } sealed interface Event { data class OnTransitionToEditPage(...) : Event }

Slide 39

Slide 39 text

© ZOZO, Inc. 39 ● UIへのユーザーインプット
 ○ Actionとしてまとめる
 ○ ActionによってStateの更新、Eventの発行を実行
 
 コーディネート詳細: データの流れの整理
 
 fun onClickEditCoordinate(coordinate: CoordinateDetail) { _navigateToEditCoordinate.value = Event(coordinate) }

Slide 40

Slide 40 text

© ZOZO, Inc. 40 sealed interface Action { data class EditCoordinateDetail(...) : Action } fun dispatchAction(action: Action) { when (action) { is Action.EditCoordinateDetail -> _event.emit( Event.OnTransitionToEditPage(...) ) } }

Slide 41

Slide 41 text

© ZOZO, Inc. 41 ● データの流れが単方向(UDF)になるよう調整
 
 コーディネート詳細: データの流れの整理
 


Slide 42

Slide 42 text

© ZOZO, Inc. 42 ● Composeの実装をラップしたCustomViewで各要素を置換
 
 コーディネート詳細: UI要素のComposeへの書き換え
 


Slide 43

Slide 43 text

© ZOZO, Inc. 43 ● コーディネート画像を表示する要素
 ● CoordinateImageViewとしてCustomView化
 ● Fragmentにロジックが直接実装
 ○ まずロジックをCustomViewに移動
 ○ リファクタリング後Composeへの置換開始
 
 コーディネート詳細: UI要素のComposeへの書き換え
 


Slide 44

Slide 44 text

© ZOZO, Inc. 44 ● AbstractComposeViewを継承してCustomViewを作成
 ● Content関数でComposeを実装
 class CoordinateImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { @Composable override fun Content() { FaansTheme { CoordinateImage(...) } } } @Composable fun CoordinateImage(...) {} コーディネート詳細: UI要素のComposeへの書き換え
 


Slide 45

Slide 45 text

© ZOZO, Inc. 45 class CoordinateImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var coordinate by mutableStateOf(null) var coordinateItems = mutableStateListOf() @Composable override fun Content() { FaansTheme { CoordinateImage(coordinate, coordinateItems) } } } @Composable fun CoordinateImage( coordinate: CoordinateDetail?, coordinateItems: List, ) {...}

Slide 46

Slide 46 text

© ZOZO, Inc. 46 class CoordinateImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var coordinate by mutableStateOf(null) var coordinateItems = mutableStateListOf() @Composable override fun Content() { FaansTheme { CoordinateImage(coordinate, coordinateItems) } } } @Composable fun CoordinateImage( coordinate: CoordinateDetail?, coordinateItems: List, ) {...}

Slide 47

Slide 47 text

© ZOZO, Inc. 47 ● 通常のCustomViewと同様にlayout.xmlに記述
 ● データを渡したい場合も同様に可能
 ● CustomViewの利用側はComposeで実装されているかを
 意識する必要が無い
 binding.coordinateImageView.run { this.coordinate = coordinate coordinateItems.addAll(state.coordinateItems) } コーディネート詳細: UI要素のComposeへの書き換え
 
 . . .

Slide 48

Slide 48 text

© ZOZO, Inc. 48 ● Callbackを処理したい場合は?
 ● 関数型でStateを宣言し結果をラムダで受け取る
 コーディネート詳細: UI要素のComposeへの書き換え
 
 class MyButtonView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var onClick by mutableStateOf<() -> Unit>({}) @Composable override fun Content() { FaansTheme { MyButton(onClick) } } } binding.myButtonView.onClick = {}

Slide 49

Slide 49 text

© ZOZO, Inc. 49 ● CoordinateImageViewと同様に各要素をCustomView化
 ● layout.xmlを置き換えていく
 コーディネート詳細: UI要素のComposeへの書き換え
 


Slide 50

Slide 50 text

© ZOZO, Inc. 50

Slide 51

Slide 51 text

© ZOZO, Inc. 51 ● 画面のほとんどの要素がComposeに移行
 ● 残るTopAppBarをComposeへ移行
 ● 画面全体をComposeで書き換える
 コーディネート詳細: 画面全体をComposeへ書き換え
 
 @Composable fun CoordinateDetailScreen( state: State = State.Initial, actionDispatcher: (Action) -> Unit = { _ -> }, ) { Scaffold( topBar = { TopAppBar(state, actionDispatcher) } ) {...} }

Slide 52

Slide 52 text

© ZOZO, Inc. 52 Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()), ) { CoordinateImage(...) if (!state.coordinateReviewComment.isNullOrEmpty()) { ReviewComment(...) } else { CoordinateCounts(...) } TextAboutCoordinate(...) PublishDate(...) CoordinateItemAndTag(...) }

Slide 53

Slide 53 text

© ZOZO, Inc. 53 ● 画面全体をComposeで置換完了
 ● layout.xmlは削除
 ● CustomViewも削除
 ● 実装したComposableだけが残る
 コーディネート詳細: 画面全体をComposeへ書き換え
 


Slide 54

Slide 54 text

© ZOZO, Inc. 54 class CoordinateImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var coordinate by mutableStateOf(null) var coordinateItems = mutableStateListOf() @Composable override fun Content() { FaansTheme { CoordinateImage(coordinate, coordinateItems) } } } @Composable fun CoordinateImage( coordinate: CoordinateDetail?, coordinateItems: List, ) {...}

Slide 55

Slide 55 text

© ZOZO, Inc. 55 @Composable fun CoordinateImage( coordinate: CoordinateDetail?, coordinateItems: List, ) {...}

Slide 56

Slide 56 text

© ZOZO, Inc. 56 ● ComposeのNavigation Componentへ移行することで削除可能
 ● 全てのFragmentをComposeのラッパーとした後移行、削除
 ● 現状FAANSではFragmentベースのNavigation Componentを使用
 
 参考: https://developer.android.com/jetpack/compose/navigation#navigate-fro m-Compose
 
 Fragment自体の削除
 


Slide 57

Slide 57 text

© ZOZO, Inc. 57 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 58

Slide 58 text

© ZOZO, Inc. 58 ● FAANSとは
 ● FAANSの開発チーム
 ● FAANS Androidの技術スタック
 ● Jetpack Composeへの書き換え事例紹介
 ● まとめ
 アジェンダ


Slide 59

Slide 59 text

© ZOZO, Inc. 59 ● Jetpack Composeは書いていて楽しい
 ● 画面の一部の要素のみ置き換えるといった段階的な移行が可能
 ○ Jetpack Composeの相互運用APIは強力
 ○ ViewベースなUIと組み合わせて使用可能
 ○ 既存アプリへも無理なく導入し開発を始められるのでオススメ
 まとめ


Slide 60

Slide 60 text

No content