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

既存画面の Jetpack Composeでの書き換え: FAANSでの事例紹介 / Case study of rewriting existing screens with Jetpack Compose

既存画面の Jetpack Composeでの書き換え: FAANSでの事例紹介 / Case study of rewriting existing screens with Jetpack Compose

Ryosuke Horie

May 23, 2022
Tweet

More Decks by Ryosuke Horie

Other Decks in Programming

Transcript

  1. 既存画面の

    Jetpack Composeでの書き換え:

    FAANSでの事例紹介

    2022/5/23 

    ZOZO Tech Talk #7 - Android

    株式会社ZOZO

    ブランドソリューション開発本部 FAANS部 フロントエンド

    テックリード

    堀江 亮介

    Copyright © ZOZO, Inc.
    1

    View Slide

  2. © ZOZO, Inc.
    株式会社ZOZO

    ブランドソリューション開発本部 FAANS部 フロントエンド

    テックリード
    堀江 亮介

    ● 自動化とビールが好き
    ● 最近は家族でドライブすることが多い
    ● @Horie1024

    2

    View Slide

  3. © ZOZO, Inc.
    3
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  4. © ZOZO, Inc.
    4
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. © ZOZO, Inc.
    8
    ● Fashion Advisors are Neighbors略

    ● 「ショップスタッフの

    効率的な販売をサポートする

    ショップスタッフ専用ツール」

    ● Web, iOS, Androidで提供

    FAANSとは

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

    View Slide

  9. © ZOZO, Inc.
    9
    OMOプラットフォーム「ZOZOMO」

    2022年3月期 第2四半期 決算説明会補足資料 - 株式会社ZOZO, https://d31ex0fa3i203z.cloudfront.net/assets/ja/ir/pdf/J_webup_FY2021_2Q.pdf

    View Slide

  10. © ZOZO, Inc.
    10
    スタッフコーデの連携

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

    View Slide

  11. © ZOZO, Inc.
    11
    実店舗在庫の連携

    2022年3月期 第2四半期 決算説明会補足資料 - 株式会社ZOZO, https://d31ex0fa3i203z.cloudfront.net/assets/ja/ir/pdf/J_webup_FY2021_2Q.pdf

    View Slide

  12. © ZOZO, Inc.
    12
    実店舗在庫の連携

    2022年3月期 決算説明会補足資料 - 株式会社ZOZO, https://corp.zozo.com/wp-content/uploads/2022/04/JP_22034Q.pdf

    View Slide

  13. © ZOZO, Inc.
    13
    実店舗在庫の連携

    2022年3月期 決算説明会補足資料 - 株式会社ZOZO, https://corp.zozo.com/wp-content/uploads/2022/04/JP_22034Q.pdf

    View Slide

  14. © ZOZO, Inc.
    14
    実店舗在庫の連携

    2022年3月期 決算説明会補足資料 - 株式会社ZOZO, https://corp.zozo.com/wp-content/uploads/2022/04/JP_22034Q.pdf

    View Slide

  15. © ZOZO, Inc.
    15
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  16. © ZOZO, Inc.
    16
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  17. © ZOZO, Inc.
    17
    FAANSの開発チーム

    Miroを見る

    View Slide

  18. © ZOZO, Inc.
    18
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  19. © ZOZO, Inc.
    19
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  20. © ZOZO, Inc.
    20
    FAANS Androidの技術スタック

    Miroを見る

    View Slide

  21. © ZOZO, Inc.
    21
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  22. © ZOZO, Inc.
    22
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  23. © ZOZO, Inc.
    23
    ● FAANSでは去年の8月に導入

    ● フルComposeではない

    ○ アプリの基本構成はSingle Activity + Navigation Component

    ○ ViewベースなUIとComposeで作られたUIが混在 

    Jetpack Compose使ってますか?


    View Slide

  24. © ZOZO, Inc.
    24
    ● ZOZOでの導入状況

    ○ ZOZOTOWNでは導入済み

    ○ WEARは近日中

    Jetpack Compose使ってますか?

    ZOZOTOWN AndroidへのJetpack Compose導入の取り組み - ZOZO TECH BLOG,
    https://techblog.zozo.com/entry/zozotown-android-jetpack-compose

    View Slide

  25. © ZOZO, Inc.
    25
    ● 開発効率が向上

    ○ 直感的な宣言型API

    ○ シンプルなコードでのUIの記述が可能

    例: UIの出し分け、リスト表示

    ○ 既存のViewベースなUIとの相互運用が容易

    ○ ドキュメントやサンプルコードが豊富

    ● 一部の既存ViewベースUIと相性が悪い

    ○ 例: BottomSheetDialogFragment

    Jetpack Composeを導入してどうだったか?


    View Slide

  26. © ZOZO, Inc.
    26
    ● FAANSではViewベースなUIとComposeで作られたUIが混在 

    ● 今後のUI実装方針をどうするか?

    ○ UIは基本的にComposeで開発する方針に統一


    FAANSでのUI実装方針


    View Slide

  27. © ZOZO, Inc.
    27
    ● 画面全体を一度に書き換えるのは難しい

    ○ リソース、タスク優先度、開発期間 etc…

    ● 相互運用APIの存在

    ○ 既存ViewベースUIとComposeを組み合わせて実装可能

    ○ 既存画面のUI要素を1つずつComposeへ移行可能

    ○ https://developer.android.com/jetpack/compose/interop

    ● 既存画面を段階的にComposeに書き換えていく

    既存画面のJetpack Composeでの書き換え


    View Slide

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


    View Slide

  29. © ZOZO, Inc.
    29
    1. データの流れの整理

    2. UI要素のComposeへの書き換え

    3. 画面全体をComposeへ書き換え


    書き換えの流れ


    View Slide

  30. © ZOZO, Inc.
    30
    ● UI Stateに画面の描画に必要な情報をまとめる

    ● UIの状態公開は1箇所に制限

    例. 複数のLiveDataの公開は避ける

    ● 単方向データフロー(UDF)に沿わせる

    1.データの流れの整理


    Android Developers アプリ アーキテクチャ ガイド UIレイヤ,
    https://developer.android.com/jetpack/guide/ui-layer

    View Slide

  31. © ZOZO, Inc.
    31
    ● 画面を複数の要素に分解

    ● 要素をComposeで再実装し置換

    ● 要素単位でCustomViewを作成

    CustomViewでComposeの実装をラップ


    2.UI要素のComposeへの書き換え


    View Slide

  32. © ZOZO, Inc.
    32
    ● layout.xmlを削除

    ● Composableのみで画面を構成


    3.画面全体をComposeへ書き換え


    View Slide

  33. © ZOZO, Inc.
    33
    1. データの流れの整理

    2. UI要素のComposeへの書き換え

    3. 画面全体をComposeへ書き換え


    書き換えの流れ


    ● 2を複数回繰り返し、段階的にComposeへ移行

    ● 最終的に画面全体をComposeへ移行


    View Slide

  34. © 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

    View Slide

  35. © ZOZO, Inc.
    35
    ● 複数のLiveDataが公開

    ○ 状態の公開は1つに制限

    ○ データは1つのデータクラスにまとめる

    ○ Eventクラスでラップした値(1度だけ処理したい)

    ■ 状態とは別に公開


    コーディネート詳細: データの流れの整理


    private val _navigateToEditCoordinate = MutableLiveData>()
    override val navigateToEditCoordinate: LiveData>
    get() = _navigateToEditCoordinate

    View Slide

  36. © 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
    }

    View Slide

  37. © 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
    }

    View Slide

  38. © 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
    }

    View Slide

  39. © ZOZO, Inc.
    39
    ● UIへのユーザーインプット

    ○ Actionとしてまとめる

    ○ ActionによってStateの更新、Eventの発行を実行


    コーディネート詳細: データの流れの整理


    fun onClickEditCoordinate(coordinate: CoordinateDetail) {
    _navigateToEditCoordinate.value = Event(coordinate)
    }

    View Slide

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

    View Slide

  41. © ZOZO, Inc.
    41
    ● データの流れが単方向(UDF)になるよう調整


    コーディネート詳細: データの流れの整理


    View Slide

  42. © ZOZO, Inc.
    42
    ● Composeの実装をラップしたCustomViewで各要素を置換


    コーディネート詳細: UI要素のComposeへの書き換え


    android:layout_width="match_parent"
    android:layout_height="wrap_content">







    ...>




    View Slide

  43. © ZOZO, Inc.
    43
    ● コーディネート画像を表示する要素

    ● CoordinateImageViewとしてCustomView化

    ● Fragmentにロジックが直接実装

    ○ まずロジックをCustomViewに移動

    ○ リファクタリング後Composeへの置換開始


    コーディネート詳細: UI要素のComposeへの書き換え


    View Slide

  44. © 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への書き換え


    View Slide

  45. © 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,
    ) {...}

    View Slide

  46. © 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,
    ) {...}

    View Slide

  47. © ZOZO, Inc.
    47
    ● 通常のCustomViewと同様にlayout.xmlに記述

    ● データを渡したい場合も同様に可能

    ● CustomViewの利用側はComposeで実装されているかを

    意識する必要が無い

    binding.coordinateImageView.run {
    this.coordinate = coordinate
    coordinateItems.addAll(state.coordinateItems)
    }
    コーディネート詳細: UI要素のComposeへの書き換え


    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    ...>
    .
    .
    .

    View Slide

  48. © 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 = {}

    View Slide

  49. © ZOZO, Inc.
    49
    ● CoordinateImageViewと同様に各要素をCustomView化

    ● layout.xmlを置き換えていく

    コーディネート詳細: UI要素のComposeへの書き換え


    View Slide

  50. © ZOZO, Inc.
    50
    android:layout_width="match_parent"
    android:layout_height="wrap_content">







    View Slide

  51. © ZOZO, Inc.
    51
    ● 画面のほとんどの要素がComposeに移行

    ● 残るTopAppBarをComposeへ移行

    ● 画面全体をComposeで書き換える

    コーディネート詳細: 画面全体をComposeへ書き換え


    @Composable
    fun CoordinateDetailScreen(
    state: State = State.Initial,
    actionDispatcher: (Action) -> Unit = { _ -> },
    ) {
    Scaffold(
    topBar = {
    TopAppBar(state, actionDispatcher)
    }
    ) {...}
    }

    View Slide

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

    View Slide

  53. © ZOZO, Inc.
    53
    ● 画面全体をComposeで置換完了

    ● layout.xmlは削除

    ● CustomViewも削除

    ● 実装したComposableだけが残る

    コーディネート詳細: 画面全体をComposeへ書き換え


    View Slide

  54. © 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,
    ) {...}

    View Slide

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

    View Slide

  56. © ZOZO, Inc.
    56
    ● ComposeのNavigation Componentへ移行することで削除可能

    ● 全てのFragmentをComposeのラッパーとした後移行、削除

    ● 現状FAANSではFragmentベースのNavigation Componentを使用


    参考:
    https://developer.android.com/jetpack/compose/navigation#navigate-fro
    m-Compose


    Fragment自体の削除


    View Slide

  57. © ZOZO, Inc.
    57
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  58. © ZOZO, Inc.
    58
    ● FAANSとは

    ● FAANSの開発チーム

    ● FAANS Androidの技術スタック

    ● Jetpack Composeへの書き換え事例紹介

    ● まとめ

    アジェンダ


    View Slide

  59. © ZOZO, Inc.
    59
    ● Jetpack Composeは書いていて楽しい

    ● 画面の一部の要素のみ置き換えるといった段階的な移行が可能

    ○ Jetpack Composeの相互運用APIは強力

    ○ ViewベースなUIと組み合わせて使用可能

    ○ 既存アプリへも無理なく導入し開発を始められるのでオススメ

    まとめ


    View Slide

  60. View Slide