59
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jetpack Composeでパフォーマンスが良くない書き方を避ける

Last updated at Posted at 2024-03-08

基本的には「早すぎた最適化は避けましょう」が前提にはあるのですが、パフォーマンス的に気をつけた方がいい書き方を紹介します。

0. Jetpack Compose の最新バージョンを使用する

書き方の前に、Jetpack Compose は新しいバージョンほどパフォーマンス改善が入っているので、極力新しいバージョンを使いましょう。

  • Modifier の Node 化
  • Compose 1.7.0 で入る strong skip mode

1. derivedStateOf は必要以上に使わない

変更のバッファリングをするためコストがかかる処理なので、頻繁に値が変わるけど結果が変わらなくて recompose を避けたい時だけに使用しましょう。
単純な値変更による recompose で問題ない時は derivedStateOf を使う必要はないです。

 tabs.forEachIndexed { index, tab ->
    // ❌
    val isSelected = remember { 
        derivedStateOf { pagerState.currentPage == index}
    }
    // ⭕️
    val isSelected = currentPage == index
    Tab(
        selected = isSelected,
        ...
    )
 }

 val items by viewModel.items.collectAsState()
 // ❌
 val itemCount by remember {
     derivedStateOf { items.count }
 }
 // ⭕️
 val itemCount = items.count 

2. 頻繁な LaunchedEffect の再起動を避ける

coroutines のキャンセルと再起動が行われるので LaunchedEffect の key に頻繁に変わりうる値を設定しないようにしましょう。
頻繁に値が変わるものを使う場合は snapshotFlow で購読しましょう。

 // ❌
 LaunchedEffect(pagerState.currentPage) {
    // Do anything
 }
 // ⭕️
 LaunchedEffect(pagerState) {
    snapshotFlow { pagerState.currentPage }
        .collect {
            // Do anything
        }
 }

3. List の操作はキャッシュさせる

Composable fun の内部で直接 List の操作を行うと recompose 時に List の操作が都度行われてしまうので避けましょう。
remember {} でキャッシュさせるか、ViewModel などですでに操作された後の状態で保持するようにしましょう。

 // ❌
 val labelIds = labelList.map { it.id }
 // ⭕️
 val labelIds = remember(labelList) {
  labelList.map { it.id }
}

4. Lazy 系のスコープ内で State にアクセスしない

LazyListScope 内で State にアクセスすると State の更新で LazyListScope が何度も作り直されることになり、Lazy 系が頻繁に recompose されるのでパフォーマンスが悪くなります。
LazyListScope 内では状態が変わるものを参照しないようにしましょう。

// ❌
val listState = rememberLazyListState()
LazyColumn(state = listState) {
  items(items) {
      Text(text = it)
  }
  if (listState.firstVisibleItemIndex + listState.layoutInfo
          .visibleItemsInfo.size == listState.layoutInfo.totalItemsCount) {
      // Do anything
  }
}

// ⭕️
val isReachedBottom by remember {
  derivedStateOf {
      listState.firstVisibleItemIndex + listState.layoutInfo
          .visibleItemsInfo.size == listState.layoutInfo.totalItemsCount
  }
}
LaunchedEffect(isReachedBottom) {
  if (isReached) {
      // Do anything
  }
}
LazyColumn(state = listState) {
  items(items) {
      Text(text = it)
  }
}

5. Log による recompose を避ける

Log の文字列で State 等を参照していると State の変更で文字列が生成されることによる recompose が発生するので SideEffect で囲むようにしましょう。

// ❌
Log.d(TAG, "List recompose ${listState.firstVisibleItemIndex}")
// ⭕️
SideEffect {
    Log.d(TAG, "List recompose ${listState.firstVisibleItemIndex}")
}

6. アニメーションや状態が頻繁に変わるものを参照するときは遅延させる

アニメーションや State のような状態が頻繁に変わりうるものを扱う場合には必要以上に recompose されないように遅延させましょう。

val color by animateColorBetween(Color.Cyan, Color.Magenta)

// ❌
Box(modifier = Modifier.background(color))
// ⭕️
Box(modifier = Modifier.drawBehind { drawRect(color) })


val alpha by animateFloatAsState(
    targetValue = if (isVisible) { 1f } else { 0f },
)
// ❌
Box(modifier = Modifier.alpha(alpha))
// ⭕️
Box(modifier = Modifier.graphicsLayer { this.alpha = alpha })

参考

59
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?