😺

Googleが公開しているAndroid開発トレーニング(初心者向け)をやってみた - Unit2

2024/12/30に公開

前回

https://zenn.dev/takoyaki31/articles/ee0a7cf7da7a66

今回

チュートリアル(Unit2):
https://developer.android.com/courses/android-basics-compose/unit-2?hl=ja

学習メモ

kotlinにはnull安全というコンセプトがあり、基本的に非NULL許容となっている。
wrapContentSize:コンポーネントのサイズをその内容のサイズに合わせるように指定する
余白がある場合は、子要素の位置を指定することができる

wrapContentSize
Box(
    modifier = Modifier
        .fillMaxSize() // 親のサイズに合わせる
        .wrapContentSize(Alignment.Center) // 子要素を中央揃え
) {
    Text(text = "Hello, Compose!")
}

コンストラクタ

・セカンダリコンストラクタは必ずプライマリコンストラクタを呼び出す(thisキーワード)
・プライマリコンストラクタを省略した場合、引数なしのデフォルトのコンストラクタが生成される

constructor
// プライマリコンストラクタ
class SmartDevice(val name: String, val category: String) {
    var deviceStatus = "online"

    // セカンダリコンストラクタ
    constructor(name: String, category: String, statusCode: Int) : this(name, category) {
        deviceStatus = when (statusCode) {
            0 -> "offline"
            1 -> "online"
            else -> "unknown"
        }
    }
    ...
}

プロパティ委譲

デリゲート:特定の処理やロジックを別のクラスやオブジェクトに任せること
デリゲートするためのクラスにインターフェース(ReadWritePropertyやReadOnlyProperty)を実装し、byキーワードでデリゲートする

RangeRegulator(デリゲート用)
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        }
    }
}
SmartTvDevice(プロパティ委譲する)
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    // プロパティの初期化、setter、getterをRangeRegulatorクラスに任せる
    private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)

    private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)

    ...

}

ラムダ式

無名関数を簡潔に書く方法

lambda
// ラムダ式
val ラムダ = { 引数:->}

引数の型は型推論があるため、省略可能
また、引数が一つの場合は引数自体が省略可能(itと命名される)

lambda省略
// 省略記法
val ラムダ = {}

クロージャのキャプチャ

クロージャ:関数とその関数が定義されたスコープの変数や状態を一緒に記憶しているオブジェクト
キャプチャ:スコープ外の変数をクロージャ内から操作、参照できる仕様のこと
以下例ではclickCountをコンポーザブルに渡してインクリメントしても値渡しになるため、インクリメントした結果が保存されない
そのため、クロージャのキャプチャの仕組みを使用してclickCountをLemonadeProcessのスコープで管理する

実装の一部抜粋
fun LemonadeProcess(modifier: Modifier = Modifier){
    var squeezeCount:Int by remember { mutableStateOf(Random.nextInt(2, 6)) }
    var clickCount: Int by remember { mutableStateOf(0) }
    when(clickCount){
        0 -> LemonadeCurrentProcess(
                modifier = modifier,
                painter = painterResource(R.drawable.lemon_tree),
                message = stringResource(R.string.lemon_tree_message),
                description = stringResource(R.string.lemon_tree),
                click = { clickCount++ }
            )
    ...
}

remember

コンポーザブル(Composable)関数はステートレスで状態を保持できないため、再コンポーズ時に状態がリセットされてしまう
下記例ではボタンを押下すると以下現象が起こる

  1. ボタンを押下する
  2. countがインクリメントされる
  3. 再コンポーズする
  4. Counter()が再実行され、count = 0になる
remember無し
@Composable
fun Counter() {
    var count = 0
    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
}

下記のようにrememberを使用すると再コンポーズしても、状態が保持されるようになり、インクリメントが反映されるようになる

remember有り
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) } // 状態を保持
    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
}

Discussion