Jetpack Compose v1.5.0に気になったAPIのまとめ

[Jetpack Compose `v1.5.0` stable になりました](https://twitter.com/JimSproch/status/1638604000881967104)🥳。

今回もたくさんのAPIの追加や更新がありますし、その中で個人的に気になった API を紹介しようと思います。Compose チームもまとめ記事を出しましたので、ぜひ読んでみてください。

https://android-developers.googleblog.com/2023/08/whats-new-in-jetpack-compose-august-23-release.html

今回のリリースでは [Android Dev Summit'22](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_L3n1j4ajHjJ6QccFUvW1u) で発表された Modifier.Node の改善を含めて、主に内部的なパフォーマンス向上に向けて更新が多いんですね。

https://www.youtube.com/embed/BjGX2RftXsU

そして、Compose パフォーマンスのテーマで Compose チームのメンバーたちが [android developers backstage](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc-pGh0SJ7hScCv-jepzHTuU) ポッドキャストもぜひ聴いてみてください。

<iframe width="560" height="315" src="https://www.youtube.com/embed/h-b3-DWYhjo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

# LookaheadLayout の代わりに LookaheadScope 

特に [Shared element transition](https://developer.android.com/guide/fragments/animate#shared) を実現するため追加された Lookahead API について v1.3.0 で追加されたとき軽く紹介書いたことあります。良かったらぜひ読んでみてください。

https://goodpatch-tech.hatenablog.com/entry/2022/10/31/164723#LookaheadLayout

もっと理解を深めるため [Jetpack Compose Internals](https://twitter.com/ComposeInternal) の筆者 [Jorge Castillo](https://twitter.com/JorgeCastilloPr) の記事もぜひ読んでみてください。

https://newsletter.jorgecastillo.dev/p/introducing-lookaheadlayout

v1.5.0 以降 `LookaheadLayout` の代わりに新しい `LookaheadScope` を使えるようになってます（[v1.6.0 で LookaheadLayout がもう deprecate される動き](https://android-review.googlesource.com/c/platform/frameworks/support/+/2635372)も見られます）。

```kotlin
// v1.5.0 以下
Column(modifier = Modifier..) {
    LookaheadLayout(modifier = Modifier.fillMaxSize()) { /* .. */ }
}

// v1.5.0 以降
Column(modifier = Modifier..) {
    LookaheadLayoutScope { /* .. */ }
}
```
LookaheadLayout は元々 `Row`、`Column` みたいな layout で、子供たちの MeasurePolicy（サイズ調整と配置）は LookaheadLayout に依存しました。
LookaheadScope は layout ではないので、子供たちの MeasurePolicy は LookaheadScope が追加される layout に依存します。つまり、LookaheadLayout という特別な layout と依存性がなくなります。

そして、Lookahead API で追加された２つの Modifier `Modifier.placed` と `Modifier.onIntermediatedLayout` でも変更点があります。 

### Modifier.intermediateLayout
Modifier.intermediateLayout はアニメーション途中のフレームで子供たちの MeasurePolicy（サイズ調整と配置）を計算するためのコールバックです。
ここで `lookaheadSize` というアニメーションが終わり次第の子供たちのサイズをパラメータとして以前取得しましたが、これからはコールバックのスコープ `IntermediateLayoutScope` のプロパティとして使えるようになってます。

```kotlin
// v1.5.0 以下
Modifier.intermediateLayout { measurable, constraints, lookaheadSize  ->
    val targetSize = lookaheadSize
    /* targetSize までアニメーションを実行する */
}

Modifier.intermediateLayout { measurable, constraints -> 
    val targeSize = this.lookaheadSize
    /* targetSize までアニメーションを実行する */
}
// v1.5.0 以降
```

### Modifier.onPlaced
アニメーションが終わり次第子供たちが配置される位置を `Modifier.onPlaced` で予想して調整できてました。**このメソッドを使えなくなってます**。
このメソッドで取得する `lookaheadScopeCoordinates` を `Modifier.intermediateLayout` で取得できますが、`PlacementScope` にスコープされるようになったため、配置されるフェーズのコールバックの `layout{}` のみでアクセスできるようになってます。

```kotlin
// v1.5.0 以下
Modifier.onPlaced { lookaheadScopeCoordinates, layoutCoordinates -> 
    val targetOffset = lookaheadScopeCoordinates.localLookaheadPositionOf(layoutCoordinates)
    /* targetOffset までアニメーションを実行する */
}

// v1.5.0 以降
Modifier.intermediateLayout {
    // ここ lookaheadScopeCoordinates 取得できない
    ..
    layout(..) { 
        coordinates?.let {
            val targetOffset = lookaheadScopeCoordinates.localLookaheadPositionOf(coordinates)
            /* targetOffset までアニメーションを実行する */
        }
    }
}
```

LookaheadScope を使って Lookahead API のサンプルコードが更新されてますので、ぜひみて見てください。

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/

# ModalBottomSheet の追加点
これは compose.material3 ではなく compose.material の BottomSheet API のことです。

### スワイプジェスチャーを有無にするフラグ
[`ModalBottomSheet`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt)をユーザー操作でスワイプジェスチャーを有無できるフラグ `sheetGestureEnabled` が追加されてます。

```kotlin
ModalBottomSheetLayout(
    sheetGestureEnabled = /* デフォルトは true */
)
```
## スクロールの進捗を取得できる
[`ModalBottomSheetState`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt%3Bl=187)で現在の anchor（[`Hidden`, `Expanded` や `HalfExpanded`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt%3Bl=78?q=modalbottomsheetstate)）と最も近い anchor の間の進捗を取得できるため新しい`progress`が追加されました。

[`BottomSheetState`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt%3Bl=125)と[`BottomDrawerState`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt%3Bl=281)でもこの値が追加されてます。

```kotlin
val bottomSheetState = rememberModalBottomSheetState()

ModalBottomSheetLayout(sheetState = state, ..) {
    Text("Progress ${state.progress}")
    ..
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966247/671f5c20-ca40-5bdb-18ab-6141041db723.gif" width="300"/>


# TextStyle.merge()
パフォーマンス向上に向けた対応で、`TextStyle`のすべてのパラメーターを含めた新しい`TextStyle.merge()`メソッドが追加されました。

**ドキュメンテーションで`TextStyle.copy()`より`TextStyle.merge` の利用を推奨されてます。**

```kotlin
// v1.5.0 以下
val customStyle = MaterialTheme.typography.bodyLarge.copy(
    fontWeight = FontWeight.Bold, 
    fontSize = 14.sp
)

// v1.5.0 以降
val customStyle = MaterialTheme.typography.bodyLarge.merge(
    fontWeight = FontWeight.Bold, 
    fontSize = 14.sp
)
```

# mutableXXStateOf
`v1.5.0` では `int`、`double`、`long`と`float`タイプの新しい `mutableStateXXOf` メソッドが追加されました。

```kotlin
// v1.5.0 以下
var count by remember { mutableStateOf(10) } 
var progress by remember { mutableStateOf(0f) }

// v1.5.0 以降
var count by remember { mutableStateIntOf(10) }
var progress by remember { mutableStateFloatOf(0) }
```

この4つのタイプの場合 `mutableStateOf` をそのまま使うと以下のような lint warning が表示されます。

> **Prefer mutableFloatStateOf instead of mutableStateOf**
> 
> Inspection info:Calling mutableStateOf<T>() when T is either backed by a primitive type on the JVM or is a value class results in a state implementation that requires all state values to be boxed. This usually causes an additional allocation for each state write, and adds some additional work to auto-unbox values when reading the value of the state.

また Compose のパフォーマンス向上に向けた対応ではないかと考えてます。

この lint warning の ID は `AutoboxingStateCreation`と警告レベルが information になってます。もし、無効や警告レベルを上げたいなら以下のように調整できます。
```kotlin
// 無効にする
@Suppress("AutoboxingStateCreation")
@Composable
fun Sample(){..}

// 警告レベルを warning に上げる
// build.gradle
android {
    lint {
        // build.gradle.ktsの場合 
        // warning.add("AutoboxingStateCreation")
        warning AutoboxingStateCreation
    }
}
```

## drawText で BlendMode 追加
Canvas の drawText メソッドで `BlendMode` パラメータを渡せるようになってます。

```kotlin
Canvas {
    drawText(text = "Blend", ..)
    drawText(text = "Exclusion", blendMode = BlendMode.Exclusion, ..)
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966247/8d480028-47e1-577f-e6dd-38d9885dbd55.png"/>

## Modifier.pointerInput が lazy になった
API変化がなく、パフォーマンス向上のために内部的な改善で Modifier.pointerInput が lazy になってます。
下記の比較では、v1.5.0以降はユーザーが操作するまで `pointerInput{ }` ブロックも実行されないことを確認できますね。

|v1.5.0以下| v1.5.0以降|
|--|--|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966247/0c02dd36-121e-241f-0b68-9404edd8136f.gif" width="300" />|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966247/085c44f2-8ab9-9e63-0f0d-466d99af6806.gif" width="300"/>|

```kotlin
Modifier.pointerInput(Unit) {
    // <-- v1.5.0 はここもユーザー操作まで実行されない
    awaitPointerEventScope {..}
}
```

詳しくこの記事を見てみてください。

https://medium.com/androiddevelopers/improvements-and-changes-to-composes-pointer-input-6026904ac972


# 終わりに
BasicText2、Modifier.Node 改善を含めて他にも、ここに書いてない v1.5.0 のAPI、特にパフォーマンス向上に向けて、更新が沢山あります。次は、9月14-15日 DroidKaigi 2023 ですね！盛り上がりましょう！🥳

http://2023.droidkaigi.jp/