2019/11/26にサイボウズ社内で開催した勉強会の資料です。 Jetpack ComposeがまだStableになるずっと前の情報をまとめたものになります。
Jetpack Compose 完全に理解したϞόΠϧνʔϜҪాҰฏ
View Slide
Jetpack Compose ってなに?2🤔
Kotlin でめっちゃええ感じに UI を作るやつhttps://developer.android.com/jetpack/compose3
めっちゃええ感じ?Less CodeIntuitive 💡Accelerate Development 🚄Powerful ⚡~.xml は不要になり、Kotlin だけで UI を作成可能UI を宣⾔するだけ。状態が変化すると UI は⾃動更新既存のコードと完全に互換。Android Studio がライブプレビューMaterial Design, ダークテーマ, アニメーションなどをサポート4
すごそう👏5
Jetpack Compose で 何が変わる?6🤔
“Jetpack Compose”7🤔
Jetpack = Unbundle なツールキット🚀• Android は OS アップデートの普及が遅い 😨• Android 9.0 のシェア: 10.4 %• iOS 13 のシェア: 50 %• OS バージョンに依存する API は中々使われない 🤢• 新しい API を OS アップデートに含めて配布しても使われない 🤮• Support library → Jetpack8
“Jetpack Compose”9🤔🚀
Compose = UI を “構成する” 何か10weblio より
今までの “UI を構成する”111. データが与えられたとき、何を表⽰するか2. イベントに対して何をするか3. UI は時間経過でどのように変化すべきか4. レイアウトの定義 (my_fragment.xml, attrs.xml, styles.xml )
3. UI は時間経過でどのように変化すべきか1299+1010 99+ 1099+99+
命令型プログラミングで時系列を記述する⾟さ1399+10fun updateCount(count: Int) {if (count == 0) {removeBadge()} else if (count <= 99 && hasBadge()) {setBadgeText(count)} else if ...}
Jetpack Compose の “UI を構成する”141. データが与えられたとき、何を表⽰するか2. イベントに対して何をするか3. UI は時間経過でどのように変化すべきか4. レイアウトの定義 (my_fragment.xml, attrs.xml, styles.xml )
Jetpack Compose基礎編15
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Greeting("Ippei")}}}@Composablefun Greeting(name: String) {Text("Hello $name")}UI を表⽰する16
UI は関数の組み合わせで構成する• @Composable をつける• Composable 関数は データを UI に変換する• データは引数として受け取る• Composable 関数は Composable 関数を呼び出せる17@Composablefun Greeting(name: String) {Text("Hello $name")}
@Composablefun Greeting(name: String) {if (name == "Ippei") {Text("Hello $name")} else {Text(text = "You are not Ippei")}}@Composablefun Greeting(names: List) {for (name in names) {Text("Hello $name")}}18
19
@Composablefun 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
UI を更新するときは Composable に新しいデータを渡す21Data UIData UIComposableUI を更新する
• ViewModel, LiveData, Rx, Coroutines + Flow• View が ViewModel のデータを継続して購読する22DataView(UI)Reactive programmingViewModel Data
• @Model を付けたクラスは、観測可能 & スレッドセーフになる• Composable 関数は、Model のプロパティを⾃動で購読23@ModelComposable UIUIDataDataModel
@Modeldata class LikeButtonState(var count: Int = 0)@Composablefun LikeButton(state: LikeButtonState = LikeButtonState()) {Button(text = "${state.count} Like",onClick = { state.count++ })}@Model で状態管理24
Single source of truth *25
View の持つ状態は誰が管理している?26誰が View のイベントに反応する?誰が状態を所有してる?誰が状態を更新する?
android.widget.CheckBox27// 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}
android.widget 😩28誰が View のイベントに反応する?誰が状態を所有してる?誰が状態を更新する?View ではない Listener を実装した誰か1つの View の状態を複数箇所で所有可能基本的に View ⾃⾝が更新するが、誰でも更新可能
Single source of truth29誰が View のイベントに反応する?誰が状態を所有してる?誰が状態を更新する?状態の所有者がイベントに反応する。状態の所有者は常に1つ。状態の所有者のみが更新する。
Single source of truth in Compose30@Modelclass FormState(var optionChecked: Boolean = false)@Composablefun Form(formState: FormState = FormState()) {Checkbox(checked = formState.optionChecked,onCheckedChange = { newState ->formState.optionChecked = newState})} ※ CheckBox の⽂⾔は別途 Text で表⽰する必要がある
Data flows 🔄31
データの流れは⼀⽅向32NewsContentNewsCardNewsCard NewsCardImage Text Image Text Image TextBusiness LogicsUserDataEvents■ = Composable
@Composablefun NewsContent(newsItems: List) {newsItems.forEach { newsItem ->NewsCard(newsItem)}}@Composablefun NewsCard(newsItem: NewsItem) {Column {SimpleImage(image = +imageResource(newsItem.imageId))Text(text = newsItem.title)}}データはトップダウンに伝搬• 引数で渡す• 親 → ⼦ へ伝搬• グローバル変数を参照しない33
イベントはボトムアップに伝搬ラムダ式を引数として 親 → ⼦ に渡す34NewsContentNewsCard NewsCardImage Text Image TextBusiness LogicsUserEvents■ = Composable{ }{ }{ }
35@Composablefun 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 })}}}@Composablefun NewsCard(newsItem: NewsItem, onClick: () -> Unit) {Clickable(onClick = onClick) {// Image ͱ Text}}※ スタイルは調整してあります
既存コードとの互換性(未実装)36
Composable → View 変換 (予定)37What's New in Jetpack Compose (Android Dev Summit ’19) のスライドより
Composable 内で View を使う38• ⾃作したコンポーネントや外部ライブラリとの共存させたい• ViewBinding ?• 既存の View をラップした Composable を⽤意する?• 既存の View は Single source of truth の設計ではない問題
🚀 Jetpack Compose ⚒391. Unbundle な UI ツールキットの提供2. 宣⾔的な記述による UI 時系列の気配りからの解放3. 簡潔な状態管理とイベントハンドリングの実現4. Kotlin の⾔語機能をフルに使ったレイアウト※ Technical Preview なのでプロダクトにはまだ使えない
Jetpack Compose の正体 🔍40UIツールライブラリComposable 関数, Material Theme, etcKotlin コンパイラプラグイン@Composable, @Model からのコード⽣成+
その他• Layout• Effects & memo• Theme• 気になる⼈は Codelab を⾒てね! https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#041