$30 off During Our Annual Pro Sale. View Details »

Jetpack Compose 完全に理解した

mkeeda
December 20, 2022

Jetpack Compose 完全に理解した

2019/11/26にサイボウズ社内で開催した勉強会の資料です。
Jetpack ComposeがまだStableになるずっと前の情報をまとめたものになります。

mkeeda

December 20, 2022
Tweet

More Decks by mkeeda

Other Decks in Programming

Transcript

  1. Jetpack Compose

    完全に理解した
    ϞόΠϧνʔϜ
    ޲ҪాҰฏ

    View Slide

  2. Jetpack Compose ってなに?
    2
    🤔

    View Slide

  3. Kotlin でめっちゃええ感じに UI を作るやつ
    https://developer.android.com/jetpack/compose
    3

    View Slide

  4. めっちゃええ感じ?
    Less Code
    Intuitive 💡
    Accelerate Development 🚄
    Powerful ⚡
    ~.xml は不要になり、Kotlin だけで UI を作成可能
    UI を宣⾔するだけ。状態が変化すると UI は⾃動更新
    既存のコードと完全に互換。Android Studio がライブプレビュー
    Material Design, ダークテーマ, アニメーションなどをサポート
    4

    View Slide

  5. すごそう👏
    5

    View Slide

  6. Jetpack Compose で

    何が変わる?
    6
    🤔

    View Slide

  7. “Jetpack Compose”
    7
    🤔

    View Slide

  8. Jetpack = Unbundle なツールキット🚀
    • Android は OS アップデートの普及が遅い 😨
    • Android 9.0 のシェア: 10.4 %
    • iOS 13 のシェア: 50 %
    • OS バージョンに依存する API は中々使われない 🤢
    • 新しい API を OS アップデートに含めて配布しても使われない 🤮
    • Support library → Jetpack
    8

    View Slide

  9. “Jetpack Compose”
    9
    🤔
    🚀

    View Slide

  10. Compose = UI を “構成する” 何か
    10
    weblio より

    View Slide

  11. 今までの “UI を構成する”
    11
    1. データが与えられたとき、何を表⽰するか
    2. イベントに対して何をするか
    3. UI は時間経過でどのように変化すべきか
    4. レイアウトの定義 (my_fragment.xml, attrs.xml, styles.xml )

    View Slide

  12. 3. UI は時間経過でどのように変化すべきか
    12
    99+
    10
    10 99+ 10
    99+
    99+

    View Slide

  13. 命令型プログラミングで時系列を記述する⾟さ
    13
    99+
    10
    fun updateCount(count: Int) {
    if (count == 0) {
    removeBadge()
    } else if (count <= 99 && hasBadge()) {
    setBadgeText(count)
    } else if ...
    }

    View Slide

  14. Jetpack Compose の “UI を構成する”
    14
    1. データが与えられたとき、何を表⽰するか
    2. イベントに対して何をするか
    3. UI は時間経過でどのように変化すべきか
    4. レイアウトの定義 (my_fragment.xml, attrs.xml, styles.xml )

    View Slide

  15. Jetpack Compose
    基礎編
    15

    View Slide

  16. class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
    Greeting("Ippei")
    }
    }
    }
    @Composable
    fun Greeting(name: String) {
    Text("Hello $name")
    }
    UI を表⽰する
    16

    View Slide

  17. UI は関数の組み合わせで構成する
    • @Composable をつける
    • Composable 関数は

    データを UI に変換する
    • データは引数として受け取る
    • Composable 関数は 

    Composable 関数を呼び出せる
    17
    @Composable
    fun Greeting(name: String) {
    Text("Hello $name")
    }

    View Slide

  18. @Composable
    fun Greeting(name: String) {
    if (name == "Ippei") {
    Text("Hello $name")
    } else {
    Text(text = "You are not Ippei")
    }
    }
    @Composable
    fun Greeting(names: List) {
    for (name in names) {
    Text("Hello $name")
    }
    }
    18

    View Slide

  19. 19

    View Slide

  20. @Composable
    fun NewsStory(newsItem: NewsItem) {
    val image = +imageResource(newsItem.imageId)
    Card(elevation = 2.dp, shape = RoundedCornerShape(4.dp)) {
    Column(crossAxisSize = LayoutSize.Expand) {
    Container(expanded = true, height = 180.dp) {
    DrawImage(image = image)
    }
    Column(modifier = Spacing(16.dp)) {
    Text(text = newsItem.title,
    maxLines = 2, overflow = TextOverflow.Ellipsis,
    style = (+themeTextStyle { h6 }).withOpacity(0.87f))
    Text(text = newsItem.author,
    style = (+themeTextStyle { body2 }).withOpacity(0.87f))
    }
    }
    }
    } 20

    View Slide

  21. UI を更新するときは Composable に新しいデータを渡す
    21
    Data UI
    Data UI
    Composable
    UI を更新する

    View Slide

  22. • ViewModel, LiveData, Rx, Coroutines + Flow
    • View が ViewModel のデータを継続して購読する
    22
    Data
    View
    (UI)
    Reactive programming
    ViewModel Data

    View Slide

  23. • @Model を付けたクラスは、観測可能 & スレッドセーフになる
    • Composable 関数は、Model のプロパティを⾃動で購読
    23
    @Model
    Composable UI
    UI
    Data
    Data
    Model

    View Slide

  24. @Model
    data class LikeButtonState(
    var count: Int = 0
    )
    @Composable
    fun LikeButton(
    state: LikeButtonState = LikeButtonState()
    ) {
    Button(
    text = "${state.count} Like",
    onClick = { state.count++ }
    )
    }
    @Model で状態管理
    24

    View Slide

  25. Single source of truth *
    25

    View Slide

  26. View の持つ状態は誰が管理している?
    26
    誰が View のイベントに反応する?
    誰が状態を所有してる?
    誰が状態を更新する?

    View Slide

  27. android.widget.CheckBox
    27
    // CheckBox ͸ CompoundButton ͷαϒΫϥε (Java)
    public abstract class CompoundButton extends Button implements Checkable {
    private static final String LOG_TAG = CompoundButton.class.getSimpleName();
    private boolean mChecked;

    // Activity or Fragment (Kotlin)
    checkBox.setOnCheckedChangeListener { checkBox, isChecked ->
    // After changed ...
    checkBox.isChecked = true
    }

    View Slide

  28. android.widget 😩
    28
    誰が View のイベントに反応する?
    誰が状態を所有してる?
    誰が状態を更新する?
    View ではない Listener を実装した誰か
    1つの View の状態を複数箇所で所有可能
    基本的に View ⾃⾝が更新するが、誰でも更新可能

    View Slide

  29. Single source of truth
    29
    誰が View のイベントに反応する?
    誰が状態を所有してる?
    誰が状態を更新する?
    状態の所有者がイベントに反応する。
    状態の所有者は常に1つ。
    状態の所有者のみが更新する。

    View Slide

  30. Single source of truth in Compose
    30
    @Model
    class FormState(var optionChecked: Boolean = false)
    @Composable
    fun Form(formState: FormState = FormState()) {
    Checkbox(
    checked = formState.optionChecked,
    onCheckedChange = { newState ->
    formState.optionChecked = newState
    }
    )
    } ※ CheckBox の⽂⾔は別途 Text で表⽰する必要がある

    View Slide

  31. Data flows 🔄
    31

    View Slide

  32. データの流れは⼀⽅向
    32
    NewsContent
    NewsCard
    NewsCard NewsCard
    Image Text Image Text Image Text
    Business Logics
    User
    Data
    Events
    ■ = Composable

    View Slide

  33. @Composable
    fun NewsContent(newsItems: List) {
    newsItems.forEach { newsItem ->
    NewsCard(newsItem)
    }
    }
    @Composable
    fun NewsCard(newsItem: NewsItem) {
    Column {
    SimpleImage(
    image = +imageResource(newsItem.imageId)
    )
    Text(text = newsItem.title)
    }
    }
    データはトップダウンに伝搬
    • 引数で渡す
    • 親 → ⼦ へ伝搬
    • グローバル変数を参照しない
    33

    View Slide

  34. イベントはボトムアップに伝搬
    ラムダ式を引数として 親 → ⼦ に渡す
    34
    NewsContent
    NewsCard NewsCard
    Image Text Image Text
    Business Logics
    User
    Events
    ■ = Composable
    { }
    { }
    { }

    View Slide

  35. 35
    @Composable
    fun NewsContent(
    newsItems: List,
    newsState: NewsState = NewsState() // Model
    ) {
    Column {
    TopAppBar(title = {
    Text(text = newsState.selectedNewsTitle ?: "not selected")
    })
    newsItems.forEach { newsItem ->
    NewsCard(
    newsItem = newsItem,
    onClick = { newsState.selectedNewsTitle = newsItem.title }
    )
    }
    }
    }
    @Composable
    fun NewsCard(newsItem: NewsItem, onClick: () -> Unit) {
    Clickable(onClick = onClick) {
    // Image ͱ Text
    }
    }
    ※ スタイルは調整してあります

    View Slide

  36. 既存コードとの互換性
    (未実装)
    36

    View Slide

  37. Composable → View 変換 (予定)
    37
    What's New in Jetpack Compose (Android Dev Summit ’19) のスライドより

    View Slide

  38. Composable 内で View を使う
    38
    • ⾃作したコンポーネントや外部ライブラリとの共存させたい
    • ViewBinding ?
    • 既存の View をラップした Composable を⽤意する?
    • 既存の View は Single source of truth の設計ではない問題

    View Slide

  39. 🚀 Jetpack Compose ⚒
    39
    1. Unbundle な UI ツールキットの提供
    2. 宣⾔的な記述による UI 時系列の気配りからの解放
    3. 簡潔な状態管理とイベントハンドリングの実現
    4. Kotlin の⾔語機能をフルに使ったレイアウト
    ※ Technical Preview なのでプロダクトにはまだ使えない

    View Slide

  40. Jetpack Compose の正体 🔍
    40
    UIツールライブラリ
    Composable 関数, Material Theme, etc
    Kotlin コンパイラプラグイン
    @Composable, @Model からのコード⽣成
    +

    View Slide

  41. その他
    • Layout
    • Effects & memo
    • Theme
    • 気になる⼈は Codelab を⾒てね!

    https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#0
    41

    View Slide