Slide 1

Slide 1 text

これでもう迷わない! Jetpack Composeの書き方 実践ガイド 株式会社ZOZO
 計測プラットフォーム開発本部 計測アプリ部 Androidブロック
 中鉢 かける Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

© ZOZO, Inc. 株式会社ZOZO 計測プラットフォーム開発本部 計測アプリ部 Androidブロック 中鉢 かける/ばっち 🏢2021年 - 株式会社サイバーエージェント Flutterアプリ開発 🏢2025年 - 株式会社ZOZO ZOZOSUITを使用したAndroidアプリ開発 2

Slide 3

Slide 3 text

© ZOZO, Inc. 3 計測プラットフォーム開発本部

Slide 4

Slide 4 text

© ZOZO, Inc. これでもう迷わない! Jetpack Composeの書き方 実践ガイド 4

Slide 5

Slide 5 text

© ZOZO, Inc. 5 「迷う」とはなにか 道に迷うなど 辿り着きたいゴールと行き方の選択肢はわかってるけど、どれがいいかわかっていない状況

Slide 6

Slide 6 text

© ZOZO, Inc. 6 「迷う」の課題 本当は5分でいけるところが10分、15分かかったり 坂道で想像以上に疲れたり 最悪の場合たどり着けなかったり

Slide 7

Slide 7 text

© ZOZO, Inc. 7 Jetpack Composeの迷う ● UIを作ったり、UIの状態管理を実装するときにどっちでも動くけどどっちがいいんだろう? ● 他の画面ではこうやって実装しているけど、別の画面では同じ動作を別の方法で書かれてる? ● modifierって絶対いるの?複雑なUIを実装するときどれくらいの単位で分割するべき?

Slide 8

Slide 8 text

© ZOZO, Inc. 8 Jetpack Composeの迷う ● UIを作ったり、UIの状態管理を実装するときにどっちでも動くけどどっちがいいんだろう? ● 他の画面ではこうやって実装しているけど、別の画面では同じ動作を別の方法で書かれてる? ● modifierって絶対いるの?複雑なUIを実装するときどれくらいの単位で分割するべき?  本当は1人日で実装できるものが1.5人日、2人日かかって開発効率が落ちる  レビューに迷って速度が落ちる その結果…

Slide 9

Slide 9 text

© ZOZO, Inc. 9 @Composable private fun ArticleCard( article: Article, collapsed: MutableState, cardModifier: Modifier = Modifier, titleModifier: Modifier = Modifier, ) { Card(modifier = cardModifier) { Padding(8.dp) { Text(article.title, modifier = titleModifier) } if (!collapsed.value) { // impl } } }

Slide 10

Slide 10 text

© ZOZO, Inc. 10 Jetpack Composeの迷う 実際にコードを書くともっと色々な迷いがある →それによって、開発効率が落ちる Flutterから来た自分は最初結構迷った

Slide 11

Slide 11 text

© ZOZO, Inc. 11 本セッションのモチベーション 自分がFlutterからAndroidに領域を変えたとき、Composeの書き方で迷うことが多くてFlutterと同じ パフォーマンスが出なかった 記事を調べたり、コードレビューする中で自分だけじゃなさそうと思った 同じ「迷い」を減らしたい!! 

Slide 12

Slide 12 text

© ZOZO, Inc. 12 迷いがなくなるイメージ 正しく、早くComposeを書けるように 1. 本来実現したいことに専念できるように ○ プロダクトが実現したいものが本来あるはず ○ それを効率よくできるだけ最短距離で実現する 2. バグも起きにくく、保守運用しやすいように ○ 常にプロダクトの成長を実現する ○ 同じ水準のコードで運用できるように https://labs.google/fx/ja/tools/whisk

Slide 13

Slide 13 text

© ZOZO, Inc. 13 でも今ってAIがすごいじゃないですか…? 生成AIがメインでコードを書いてくれる時代がこの1~2年で一気にきた このタイミングで書き方を知ることはどれくらい恩恵があるのだろうか…? でもこれは、、、  正しい情報をAIに入力できる & AIの出力を正しく判断できる知識がこれからも必要 ● 使う人がある程度スキル・知識を持っていることが前提 ● 書く知識は、読む知識にも応用できる

Slide 14

Slide 14 text

© ZOZO, Inc. 14 @Composable private fun ArticleCard( article: Article, collapsed: MutableState, cardModifier: Modifier = Modifier, titleModifier: Modifier = Modifier, ) { Card(modifier = cardModifier) { Padding(8.dp) { Text(article.title, modifier = titleModifier) } if (!collapsed.value) { // impl } } }

Slide 15

Slide 15 text

© ZOZO, Inc. 15 書く知識でAIと立ち向かう AIを使って指示を出すのは人間で、そのアウトプットに責任を持たないといけないのも人間 その良し悪しがわからず事業に致命的な問題があったとき、「AIが書いたコードなので」では説明がで きない  AIがある今だからこそ人間が正しい知識を持って、AIにインプットをしてアウトプット を評価する力が試されている

Slide 16

Slide 16 text

© ZOZO, Inc. 16 本セッションのゴール Jetpack Composeの書き方に関して迷いが減って  本質的なことに集中できるように ✓ 作りたいものを今より早く実現できる ✓ それが推奨される方法で実現できる  AIと立ち向かえるように ✓ 正しい情報をAIに入力できる ✓ AIの出力を正しく判断できる

Slide 17

Slide 17 text

© ZOZO, Inc. 17 セッションの流れ 1. Composeの基本 2. Composeのデザインパターン 3. パフォーマンスとアクセシビリティ 4. チーム開発と継続的な運用 それをどうやってチーム開発で継 続的に運用するか なぜ?がわかるための事前知識 それをどう活用するか実践的な プラクティス

Slide 18

Slide 18 text

© ZOZO, Inc. 1. Composeの基本 Composeの基本的な動作原理とUIのライフサイクルを立ち返り、 後のセクションの説明の「なぜ」を理解できるように 18

Slide 19

Slide 19 text

© ZOZO, Inc. 19 UIを構築するComposable関数は基本的にStatelessにつくる 引数であるStateをもとにUIが生成される onClickなどのイベントが発生したらComposableは上位のロジックに伝えるだけ Composeの宣言型アプローチ https://developer.android.com/develop/ui/compose/mental-model?hl=en

Slide 20

Slide 20 text

© ZOZO, Inc. 20 Composeの宣言型アプローチ https://developer.android.com/develop/ui/compose/mental-model?hl=en https://labs.google/fx/ja/tools/whisk

Slide 21

Slide 21 text

© ZOZO, Inc. 21 Composeの宣言型アプローチ https://developer.android.com/develop/ui/compose/mental-model?hl=en https://labs.google/fx/ja/tools/whisk

Slide 22

Slide 22 text

© ZOZO, Inc. 22 Composeの宣言型アプローチ https://developer.android.com/develop/ui/compose/mental-model?hl=en https://labs.google/fx/ja/tools/whisk

Slide 23

Slide 23 text

© ZOZO, Inc. 23 Composeのフェーズ Compose は、UI を更新する際に主に以下の3つのフェーズを経てフレームを描画する 1. Composition: 「何を」 表示するかを決定するフェーズ。Composable関数を実行し、UI ツリーを 構築する 2. Layout: 「どこに」 UI 要素を配置するかを決定するフェーズ。測定と配置の2つのステップから成 り、UI ツリー内の各要素のサイズと2D座標での配置を決定する 3. Drawing: 「どのように」 UI 要素を描画するかを決定するフェーズ。UI 要素がキャンバスに実際 に描画され、画面にピクセルとしてレンダリングされる。 https://developer.android.com/develop/ui/compose/phases?hl=ja

Slide 24

Slide 24 text

© ZOZO, Inc. 24 Composeのフェーズ Compose は入力が同じであれば、これらのフェーズでの作業の繰り返しを避けることでパフォーマンス を最適化する 例えば、グラフィック要素のアイコンが同じサイズで入れ替わる場合、Compose はCompositionと Layoutのフェーズをスキップし、Drawingのみを再実行可能 https://developer.android.com/develop/ui/compose/phases?hl=ja

Slide 25

Slide 25 text

© ZOZO, Inc. 25 Unidirectional Data Flow Stateが下方に流れ、イベントが上方に流れる設計パター ン(State Hoisting) テストの容易性: StateとUIを分離することで、両者を切 り離してテストしやすくできる 状態のカプセル化: 状態が 1 か所でのみ更新され、コン ポーザブルの状態に関して信頼できる情報源が 1 つだけ になるため、状態の不整合によるバグが生じる可能性が 低くなる UI の整合性: StateFlowなどのオブザーバブルな状態ホル ダーを使用すると、すべての状態の更新が UI に即座に反 映される https://youtu.be/fFLBCgoHHys?feature=shared https://developer.android.com/develop/ui/compose/architecture?hl=ja#udf

Slide 26

Slide 26 text

© ZOZO, Inc. 26 Recomposition Composeはある程度自動でスマートなRecompositionをしてくれる @Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() // 引数が前回と一緒なのでskip される } @Composable fun LoginInput() { /* ... */ } @Composable fun LoginError() { /* ... */ } https://developer.android.com/develop/ui/compose/lifecycle?hl=ja#skipping

Slide 27

Slide 27 text

© ZOZO, Inc. 27 Recomposition @Composable fun LoginScreen(modifier: Modifier = Modifier) { var showError by remember { mutableStateOf(false) } Column(modifier = modifier) { Button( onClick = { showError = !showError }, ) { Text(text = "Click Me") } if (showError) { LoginError() } LoginInput() } }

Slide 28

Slide 28 text

© ZOZO, Inc. 28 Recomposition compose-bom: 2025.07.00 Android Studio: Narwhal Feature Drop | 2025.1.2 Layout Inspector

Slide 29

Slide 29 text

© ZOZO, Inc. 29

Slide 30

Slide 30 text

© ZOZO, Inc. 30 Modifier Composableの機能と外観を拡張するために使用される UIを構築するための中核を担う ● 外部の振る舞いと外観の追加 ● APIの簡素化 ● 標準的な装飾手段 ● アクセシビリティの向上 https://developer.android.com/develop/ui/compose/modifiers-list

Slide 31

Slide 31 text

© ZOZO, Inc. 31 本セッションで対象とするプロジェクト どこまで厳密にComposeの考え方やガイドラインを適用すべきかは、開発者がなにを作るかに左右され る ● frameworkやライブラリ開発 ● アプリ開発 セッションでは一番よく使われるアプリ開発での書き方を対象にする

Slide 32

Slide 32 text

© ZOZO, Inc. 2. Composeのデザインパターン Composable関数の命名、状態管理、そしてコンポーネント分割の基準について 32

Slide 33

Slide 33 text

© ZOZO, Inc. 33 Composeのデザインパターン このセクションで話すこと 1. 命名規則 2. Separate state and events 3. コンポーネントの単一責任 4. Component or Modifier 5. 引数パラメーター

Slide 34

Slide 34 text

© ZOZO, Inc. 34 Unitを返すComposable関数名 Unit以外を返すComposable関数名 rememberを返す関数名 CompositionLocal名 1. Composeの命名規則 https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md

Slide 35

Slide 35 text

© ZOZO, Inc. Pascal Caseかつ、名詞にする 35 Composeの命名規則 - Unitを返すComposable関数名 @Composable fun LoginScreen(/*...*/) { /*...*/ Column( modifier = modifier, ) { /*...*/ } } @Composable fun loginScreen(/*...*/) { /*...*/ } @Composable fun DrawLoginScreen(/*...*/) { /*...*/ } @Composable fun createLoginScreen(/*...*/) { /*...*/ }  GOOD  BAD

Slide 36

Slide 36 text

© ZOZO, Inc. Camel Caseで命名するが、名詞がよくあるパターン 一部ヘルパーメソッドなど動詞ではじまるものもある 36 Composeの命名規則 - Unit以外を返すComposable関数名 @Composable fun painterResource(@DrawableRes id: Int): Painter { /*...*/ val imageVector = loadVectorResource(/*...*/) /*...*/ return /*...*/ } @Composable private fun loadVectorResource( /*...*/ ): ImageVector { /*...*/ } https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/andro idMain/kotlin/androidx/compose/ui/res/PainterResources.android.kt?q=file:androidx%2Fcompose%2F ui%2Fres%2FPainterResources.android.kt%20function:painterResource  GOOD

Slide 37

Slide 37 text

© ZOZO, Inc. Camel Caseで命名するが、名詞がよくあるパターン Pascal Caseにしない → UIコンポーネントとの区別を明確にするため 37 Composeの命名規則 - Unit以外を返すComposable関数名 @Composable fun PainterResource(@DrawableRes id: Int): Painter { /*...*/ }  BAD

Slide 38

Slide 38 text

© ZOZO, Inc. Camel Caseかつ、prefixにrememberをつける 永続性を伝えるため、remember{}を重複させないため 38 Composeの命名規則 - rememberを返す関数名 @Composable fun rememberLazyListState( /*...*/ ): LazyListState { return rememberSaveable() { LazyListState() } } https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/fou ndation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt?q=rememberLazyLis tState  GOOD

Slide 39

Slide 39 text

© ZOZO, Inc. Camel Caseかつ、prefixにrememberをつける 永続性を伝えるため、remember{}を重複させないため 39 Composeの命名規則 - rememberを返す関数名 @Composable fun lazyListState(/*...*/): LazyListState { /*...*/ } @Composable fun createLazyListState(/*...*/): LazyListState { /*...*/ } @Composable fun LazyListState(/*...*/): LazyListState { /*...*/ } https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/src/commonMain/ko tlin/androidx/compose/foundation/lazy/LazyListState.kt?q=rememberLazyListState  BAD

Slide 40

Slide 40 text

© ZOZO, Inc. Pascal Caseかつ、prefixにLocalをつける 40 Composeの命名規則 - CompositionLocal名 val LocalTheme = staticCompositionLocalOf() val ThemeLocal = staticCompositionLocalOf()  GOOD  BAD

Slide 41

Slide 41 text

© ZOZO, Inc. 41 2. Separate state and events 内部で状態を持たずに外部から制御可能にしてテスト、再利用性を上げる 信頼できる1つの情報源からUI構築することが理想

Slide 42

Slide 42 text

© ZOZO, Inc. 42 2. Separate state and events @Composable fun MyCheckBox( initialChecked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, ) { var checked by remember { mutableStateOf(initialChecked) } /*...*/ Checkbox( checked = checked, onCheckedChange = { checked = it onCheckedChange(it) }, modifier = modifier ) } Stateが2つになってしまい、 Single Source of Truthでない 別の箇所でinitialCheckedが変更されて recompositionされると意図しない動きにな る可能性がある  BAD

Slide 43

Slide 43 text

© ZOZO, Inc. 43 2. Separate state and events @Composable fun MyCheckBox( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, ) { /*...*/ Checkbox( checked = checked, onCheckedChange = onCheckedChange, modifier = modifier ) } Single Source of TruthなStateのみで UIを構築できている State Hoistingにより、 Unidirectional Data Flowな設計  GOOD

Slide 44

Slide 44 text

© ZOZO, Inc. 44 Composableを分割してコンポーネントに分けるとき、特にpublicなコンポーネントは分割する目的と必 要性をよく考える ● 目的:コンポーネントの目的を1つにして単一責任を意識 ● 必要性:存在する価値と認知負荷を考慮 3. コンポーネントの分割と単一責任 ● 存在を伝えるコストと気づかれずに重 複定義されるリスク ● 柔軟性がなく、類似のコンポーネント が定義される ● 既存のコンポーネントを組み合わせて 簡単につくれてしまう ● インターフェイスがシンプルで汎用的 である ● コンポーネントが存在する目的が1つ ● 存在することで価値があることが複数 ある  GOOD  BAD

Slide 45

Slide 45 text

© ZOZO, Inc. 45 3. コンポーネントの分割と単一責任 @Composable fun Button( // problem 1: button is a clickable rectangle onClick: () -> Unit = {}, // problem 2: button is a check/uncheck checkbox-like component checked: Boolean = false, onCheckedChange: (Boolean) -> Unit, ) { ... } https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#component _s-purpose  BAD

Slide 46

Slide 46 text

© ZOZO, Inc. 46 3. コンポーネントの分割と単一責任 @Composable fun Button( onClick: () -> Unit, ) { /*...*/ } @Composable fun ToggleButton( checked: Boolean, onCheckedChange: (Boolean) -> Unit, ) { /*...*/ } https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#component _s-purpose  他の分割の判断基準として、分割した単位でScreenshotTestを書きたいか、Testする 価値があるコンポーネントか考えてみる  GOOD

Slide 47

Slide 47 text

© ZOZO, Inc. 47 4. Component or Modifier horizontal paddingもRow(Spacer(), content(), Spacer())で作れるが、 Modifierで実現できるならModifier @Composable fun Padding(allSides: Dp) { // impl } // usage Padding(12.dp) { UserCard() UserPicture() } UserCard( modifier = Modifier.padding(12.dp) ) https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines .md#component-or-modifier  GOOD  BAD

Slide 48

Slide 48 text

© ZOZO, Inc. 48 4. Component or Modifier @Composable fun RecommendCard() { // impl Card( modifier = Modifier .fillMaxSize() .background(color = Color.Red) ) { // impl } } @Composable fun RecommendCard() { // impl Box( modifier = Modifier .fillMaxSize() .background(color = Color.Red) ) Card( modifier = Modifier ) { // impl } }  BAD  GOOD

Slide 49

Slide 49 text

© ZOZO, Inc. 49 5. 引数パラメーター @Composable fun TodoListItem( title: String, description: @Composable () -> Unit, isCompleted: Boolean? = null, onToggleComplete: () -> Unit, modifier: Modifier = Modifier, ) { } @Composable () -> UnitとStringの使い分け 引数の順番は? デフォルト値の nullあり・なし

Slide 50

Slide 50 text

© ZOZO, Inc. 50 5. 引数パラメーター - 順番 1. 必須パラメータ 2. Modifier(最初のオプショナルパラメーター) 3. それ以外のオプショナルパラメーター @Composable fun TopicScreen( showBackButton: Boolean, onBackClick: () -> Unit, onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, viewModel: TopicViewModel = hiltViewModel(), ) { /*...*/ } https://github.com/android/nowinandroid/blob/main/feature /topic/src/main/kotlin/com/google/samples/apps/nowinandro id/feature/topic/TopicScreen.kt

Slide 51

Slide 51 text

© ZOZO, Inc. 51 5. 引数パラメーター - Slot parameter Slot-based layoutsと言われているパターン 抽象的なインターフェイスにしてカスタマイズ性を上げてオーバーロードを削減する https://android.googlesource.com/platform/frameworks/support/+/ androidx-main/compose/docs/compose-component-api-guidelines.md #slot-parameters https://developer.android.com/develop/ui/compose/layouts/basics? hl=ja#slot-based-layouts @Composable fun TopAppBar( title: @Composable () -> Unit, /* other params */ navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, ) =

Slide 52

Slide 52 text

© ZOZO, Inc. 52 5. 引数パラメーター - Slot parameter https://android.googlesource.com/platform/frame works/support/+/androidx-main/compose/docs/co mpose-component-api-guidelines.md#slot-paramet ers @Composable fun Button( onClick: () -> Unit, text: String? = null, icon: ImageBitmap? = null ) {} @Composable fun Button( onClick: () -> Unit, text: String? = null, fontStyle: FontStyle? = null // add icon: ImageBitmap? = null ) {} @Composable fun Button( onClick: () -> Unit, text: String? = null, fontStyle: FontStyle? = null icon: ImageVector? = null, // change ) {}  BAD

Slide 53

Slide 53 text

© ZOZO, Inc. 53 5. 引数パラメーター - Slot parameter https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#sl ot-parameters @Composable fun Button( onClick: () -> Unit, text: @Composable () -> Unit, icon: @Composable () -> Unit ) {}  GOOD  privateなComposable、デザインシステムでは過度なslotは避けて厳格に実装したほう が良さそう

Slide 54

Slide 54 text

© ZOZO, Inc. 54 5. 引数パラメーター - Slot parameter https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#sl ot-parameters @Composable fun Button( onClick: () -> Unit, content: @Composable () -> Unit ) {} // usage Button(onClick = { /* handle the click */}) { Row { Icon(...) Text(...) } } 複数のslot parameterがある場合、contentだけも検討する

Slide 55

Slide 55 text

© ZOZO, Inc. 55 5. 引数パラメーター - Nullable value https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#nullabl e-parameter @Composable fun IconCard( bitmap: ImageBitmap, backgroundColor: Color = DefaultBackgroundColor, // デフォルト値にするかどうかの判断にnullを使わない elevation: Dp? = null ) { // デフォルト値を引数で最初からもらう val resolvedElevation = elevation ?: DefaultElevation } @Composable fun IconCard( bitmap: ImageBitmap, elevation: Dp = 8.dp ) { ... }  GOOD  BAD

Slide 56

Slide 56 text

© ZOZO, Inc. 56 5. 引数パラメーター - ComponentElevation objects コンポーネントの特定のプロパティ(色やエレベーションなど)が、コンポーネントの異なる状態 (enabled/disabled、focused/hovered/pressedなど)に基づいてどのように変化するかまとめる専用 のclassを検討する https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#compo nentcolor_componentelevation-objects @Composable fun Button( onClick: () -> Unit, enabled: Boolean = true, backgroundColor = if (enabled) ButtonDefaults.enabledBackgroundColor else ButtonDefaults.disabledBackgroundColor, elevation = if (enabled) ButtonDefaults.enabledElevation else ButtonDefaults.disabledElevation, content: @Composable RowScope.() -> Unit ) {}

Slide 57

Slide 57 text

© ZOZO, Inc. 57 5. 引数パラメーター - ComponentElevation objects https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#compo nentcolor_componentelevation-objects class ButtonColors( backgroundColor: Color, /* other params */ disabledContentColor: Color ) { // ロジックを専用のclassに閉じ込めることで // UI本体のAPIの肥大化を防ぐ fun backgroundColor(enabled: Boolean): Color { ... } }

Slide 58

Slide 58 text

© ZOZO, Inc. 58 5. 引数パラメーター - ComponentElevation objects https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#compo nentcolor_componentelevation-objects @Composable fun Button( onClick: () -> Unit, enabled: Boolean = true, colors: ButtonColors = ButtonDefaults.colors(), content: @Composable RowScope.() -> Unit ) { val resolvedBackgroundColor = colors.backgroundColor(enabled) }

Slide 59

Slide 59 text

© ZOZO, Inc. 59 5. 引数パラメーター - Modifier 1. 必ず引数に持つ 2. 必ずデフォルト値を設定する 3. 引数に複数持たない 4. 引数のmodifierを必ずチェーンの先頭にする 5. 引数のmodifierをトップレベルでのみ参照する 6. Modifierで表現できる引数にデフォルト値をつけない 7. チェーン済みのModifierをデフォルト値にしない

Slide 60

Slide 60 text

© ZOZO, Inc. 60 5. 引数パラメーター - Modifier @Composable fun Icon( bitmap: ImageBitmap, // no modifier parameter tint: Color = Color.Black ) @Composable fun CheckboxRow( checked: Boolean, onCheckedChange: (Boolean) -> Unit, // DON'T rowModifier: Modifier = Modifier, checkboxModifier: Modifier = Modifier ) @Composable fun Icon( bitmap: ImageBitmap, tint: Color = Color.Black, // 1: modifier is not the first optional parameter // 2: padding will be lost as soon as the user sets its own modifier modifier: Modifier = Modifier.padding(8.dp) )  BAD

Slide 61

Slide 61 text

© ZOZO, Inc. 61 5. 引数パラメーター - Modifier @Composable fun IconButton( buttonBitmap: ImageBitmap, modifier: Modifier = Modifier, tint: Color = Color.Black ) { Box(Modifier.padding(16.dp)) { Icon( buttonBitmap, // modifier should be applied to the outer-most layout // and be the first one in the chain modifier = Modifier.aspectRatio(1f).then(modifier), tint = tint ) } }  BAD

Slide 62

Slide 62 text

© ZOZO, Inc. 3. パフォーマンスと アクセシビリティ パフォーマンス低下の原因となる書き方とその回避策、主要な標準コンポーネントを使用する際のアクセシ ビリティへの配慮について 62

Slide 63

Slide 63 text

© ZOZO, Inc. 63 Recompositionのスキップと安定性 Composeは、Composableのパラメータの「安定性」に基づいて、リコンポジション中にその Composableをスキップするかどうかを判断する すべての安定したパラメータが変更されていない場合、ComposeはそのComposableの実行をスキップ する https://developer.android.com/develop/ui/compose/lifecycle?hl=ja#skipping

Slide 64

Slide 64 text

© ZOZO, Inc. 64 必要最低限な引数 個別のパラメータを使用すると、パフォーマンスが向上することもある News に title と subtitle 以上の情報が含まれている場合、News の新しいインスタンスが Header(news) に渡されるたびに、title と subtitle が変更されていなくてもコンポーザブルは再コン ポーズされる @Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. } https://developer.android.com/develop/ui/compose/architecture?hl=ja#composable-parameter

Slide 65

Slide 65 text

© ZOZO, Inc. 65 状態の読み取りを遅延させる ラムダベースのModifierを使用して読み取りを可能な限り遅延させる スクロールの度に大量にRecompositionされる @Composable private fun DetailScreen() { // impl Box(Modifier.fillMaxSize()) { val scroll = rememberScrollState(0) Title(snack, scroll.value) // impl } }

Slide 66

Slide 66 text

© ZOZO, Inc. 66 状態の読み取りを遅延させる ラムダベースのModifierを使用して読み取りを可能な限り遅延させる スクロールの度に大量にRecompositionされる @Composable private fun Title(snack: Snack, scroll: Int) { val offset = (maxOffset - scroll).coerceAtLeast(minOffset) Column( modifier = Modifier .graphicsLayer (translationY = offset) ) { … } }

Slide 67

Slide 67 text

© ZOZO, Inc. 67 状態の読み取りを遅延させる ラムダベースのModifierを使用して読み取りを可能な限り遅延させる 状態の読み取りを子のComposableや後のフェーズ(Layout、Drawing)に遅延させることで、 Recompositionの範囲を最小限に ↓Drawフェーズまでスキップさせる例 @Composable private fun DetailScreen() { // impl Box(Modifier.fillMaxSize()) { val scroll = rememberScrollState(0) Title(snack, scrollProvider = { scroll.value }) // impl } }

Slide 68

Slide 68 text

© ZOZO, Inc. 68 状態の読み取りを遅延させる ラムダベースのModifierを使用して読み取りを可能な限り遅延させる 状態の読み取りを子のComposableや後のフェーズ(Layout、Drawing)に遅延させることで、 Recompositionの範囲を最小限に ↓Drawフェーズまでスキップさせる例 @Composable private fun Title(snack: Snack, scrollProvider: ()->Int) { Column( modifier = Modifier .graphicsLayer { val offset = (maxOffset - scrollProvider()).coerceAtLeast(minOffset) translationY = offset } ) { … } }

Slide 69

Slide 69 text

© ZOZO, Inc. 69 アクセシビリティ おそらく最もよく遭遇するアクセシビリティについて Image( painter = painterResource(id = R.drawable.ic_todo_icon), contentDescription = null, modifier = modifier.padding(8.dp) ) Icon( painter = painterResource(id = if (isCompleted) R.drawable.ic_check else R.drawable.ic_uncheck), contentDescription = null, modifier = Modifier.padding(8.dp) ) よくやりませんか

Slide 70

Slide 70 text

© ZOZO, Inc. 70 アクセシビリティ https://developer.android.com/reference/kotlin/androidx/compose/material3/ package-summary#Icon(androidx.compose.ui.graphics.painter.Painter,kotlin.Str ing,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color) (中略)この画像は装飾目的で使用される 場合を除き、ユーザーが実行できる意味の あるアクションを表さない場合でも、必ず 提供する必要があります。

Slide 71

Slide 71 text

© ZOZO, Inc. 71 アクセシビリティ https://m3.material.io/components/all-buttons#9134ac95-678e-49ae- a50a-e71948011b05

Slide 72

Slide 72 text

© ZOZO, Inc. 72 アクセシビリティ https://github.com/android/nowinandroid/blob/8092c60c0f7c043a9ae 7d291fed5c29f69d6ad2d/core/designsystem/src/main/kotlin/com/goo gle/samples/apps/nowinandroid/core/designsystem/component/Chip.kt #L45

Slide 73

Slide 73 text

© ZOZO, Inc. 4. チーム開発と継続的な運用 書き方のルールをどうやってチームで継続的に運用するか 73

Slide 74

Slide 74 text

© ZOZO, Inc. 74 チーム開発での書き方の制限の難しさ ● 細かくカスタマイズすればするほど上がる認知負荷 ● 新メンバーへのルールの伝達 ● 暗黙的なルールの増加 ● ルールの継続的な運用

Slide 75

Slide 75 text

© ZOZO, Inc. 75 シンプルで柔軟なルール ● 厳格すぎるルール ○ 柔軟性の阻害 ○ 開発速度の低下 ○ チームメンバーのストレス ● 完璧を求めすぎないかつ、個人の書き方の自由のゆとりを持たせる ○ 認知負荷 ○ 持続可能性 ○ 複雑さ

Slide 76

Slide 76 text

© ZOZO, Inc. 76 AIに頼る ● ルールをドキュメント化して書き出してコンテキストにする ● コードを書いてもらうとき、レビューしてもらうとき、あらゆる場面でAIの指針となる ● 量が多くなってきたり多少複雑でもAIは判断してくれる

Slide 77

Slide 77 text

© ZOZO, Inc. 77 Lintに頼る ● 確実に速く書き方を整備する ● AIにチームごとのカスタムのルールを実装させて運用させてもいいかも ● IDEで直感的に気付けるのもいい

Slide 78

Slide 78 text

© ZOZO, Inc. 78 runtime-lint いくつかの最低限のComposeルールが含まれている ● Composable関数名 ● CompositionLocal名 ● などなど https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime-lint/src/main/java/androidx/co mpose/runtime/lint/

Slide 79

Slide 79 text

© ZOZO, Inc. 79 slack compose-lints より高機能なComposeのLintルール ● Modifierの使いまわし ● 不安定なcollection ● などなど https://slackhq.github.io/compose-lints/

Slide 80

Slide 80 text

© ZOZO, Inc. 80 AI? Lint? AI Lint 導入難易度  低い  高い 継続コスト  高い  低い 結果の一貫性  低い  高い 文脈理解  得意  限定的 実行速度  遅い  早い

Slide 81

Slide 81 text

© ZOZO, Inc. さいごに 81

Slide 82

Slide 82 text

© ZOZO, Inc. 82 まとめ 1. 迷いによる課題 a. 開発効率が落ちて本質的なことに向き合えない b. AIのアウトプットを判断するスキルが求められている 2. 解決するためのプラクティス a. Composeのデザインパターン b. パフォーマンスとアクセシビリティ c. Preview 3. プラクティスの運用について a. シンプルで柔軟なルール b. AI vs Lint

Slide 83

Slide 83 text

© ZOZO, Inc. 83 @Composable private fun ArticleCard( article: Article, collapsed: MutableState, cardModifier: Modifier = Modifier, titleModifier: Modifier = Modifier, ) { Card(modifier = cardModifier) { Padding(8.dp) { Text(article.title, modifier = titleModifier) } if (!collapsed.value) { // impl } } }

Slide 84

Slide 84 text

© ZOZO, Inc. 84 参照 ● https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compo se/docs/compose-api-guidelines.md ● https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compo se/docs/compose-component-api-guidelines.md ● https://github.com/android/nowinandroid ● https://developer.android.com/develop/ui/compose/documentation ● https://m3.material.io/components/all-buttons ● https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose /runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ ● https://slackhq.github.io/compose-lints/

Slide 85

Slide 85 text

No content