概要
Hello Worldだけ見て以下のような記事を書いていたのですが、ちゃんとYouTubeを見るべきだったので見ました。
https://qiita.com/takahirom/items/ed9fbf3788b142aad904
Understanding Compose (Android Dev Summit '19)
https://www.youtube.com/watch?v=Q9MtlmmN4Q0
Inheritance vs Composition
Composeの重要な概念としてCompositionがある。
例1
例えば、入力フォームがあって、日付の範囲を指定したい場合に、2つTextInputを持ちたい場合どうするのか?
Inheritanceだと2つDateを受け取りたいが1つしか継承できない
class Input: View{}
class ValidatedInput: Input(){}
class DateInput: ValidatedInput() {}
class DateRangeInput: ???? // ← **
ComposeのComposition Modelだと単に2回呼ぶだけで解決する
@Composable
fun DateInput(value: Date, onChange: (Date) -> Unit) {
ValidatedInput(
value = value,
onChange = onChange,
isValid = value.after(Date())
)
}
@Composable
fun DateRangeInput(value: DateRange, onChange: (DateRange) -> Unit){
DateInput(value = value.start, onChange = ...)
DateInput(value = value.end, onChange = ...)
}
例2
例えば2つの属性を持つ要素を作りたい場合はどうすればよいか?
Inheritanceだと1つしか継承できないので、同様の問題が起こる。
class FancyBox: View(){...}
class EditForm : FormView(){...}
class FancyEditForm : ???
ComposeのComposition Modelだと以下のようにclidrenの中で呼んであげるようにするだけで解決できる。
@Composable fun FancyBox(clidren: @Composable ()-> Unit) {
Box(fancy) { clidren() }
}
@Composable fun FancyEditForm(...) {
FancyBox { EditForm(...) }
}
Recompositon
Composeのヒエラルキーの一部が変わったときにすべてが変わる必要がない。
LiveDataを使っている場合の例は以下のようにobserveできる。
fun Messages(liveMsgs: LiveData<MessageData>) {
val msgs = +observe(liveMsgs)
for (msg in msgs) {
Message(msg)
}
}
もっとかんたんな例
@Composable
fun Counter() {
val count = +state { 0 }
Button(
text="Count: ${count.value} "
onPress={ count.value += 1 }
)
}
+stateで以下のようなクラスのインスタンスが作られる
@Model
class State<T> {
val value: T
}
@Model
はそのクラスのプロパティが書き込みと読み込みがobservableであることを示す。
Composeは自動的にsubscribeして更新してくれる。
How does compose work?
suspend functionと@Compose
のメソッドは似ていて、@Compose
がついているメソッドはついていないメソッドから呼び出すことができない。
fun Example(a: ()-> Unit, b: @Composable ()-> Unit) {
a()// allowed
b()// not allowed
}
Composeは"Gap buffer"と呼ばれるデータ構造を使っている。これはテキストエディタで利用されているものと同じです
Slotテーブルで管理していて、if文で状態が変わる場合どのようになるか?
@Composable fun App() {
val result = getData()
if (result == null) {
Loading(...)
} else {
Header(..)
Body(result)
}
}
コンパイル時はこのようにSlotテーブルに入れるための文が入ります。
@Composable fun App() {
val result = getData()
if (result == null) {
$composer.start(123)
Loading(...)
$composer.end()
} else {
$composer.start(456)
Header(..)
Body(result)
$composer.end()
}
}
最初に実行するときにgetData()がnullだった場合このようにSlotTableに値が入ります。
2回目にgetData()がnullじゃなかった場合に面白いことが起こり、テーブル内のデータを少し消してやり直します。
このコンセプトを"Positional Memoization"と呼ぶ。
データの変更をキャッシュしている。
コンパイル時に変更されるデータのみ監視するように変更している。