【Android】驚くほどシンプルなComposeのPullToRefreshを実装してみた

# はじめに

皆さん、ごきげんよう！れぶです！

今回の記事では、Composeのリスト表示で`PullToRefresh`機能を実装してみたので、その手順をサクッと解説していきます。

**PullToRefreshで表示するデータを更新したい方には特に参考になる**のではないかと思います。

約5分以内で読めます。それでは、参りましょう！！

# 開発環境

* MacBook Air
* Android Studio Hedgehog | 2023.1.1 Canary 9
* Kotlin
* compileSdkVersion 34
* minSdkVersion 24

# 動作イメージ

動画のように、PullToRefreshで毎回ランダムな数字をリスト表示するという超シンプルなものです。ただし、初回読み込み時はPullToRefreshのインジケーターは表示されないようにしています。

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/a8fc0c63-e4b8-b7bd-eb5e-a93de7dcb2cd.gif)

# 実装手順

## 1.依存関係を追加

```build.gradle(app)
dependencies {
    implementation "androidx.compose.material:material:latest-version"
}
```

今回は`compose.material`のPullToRefreshを使用します。以下は補足です。

- 現時点で`Accompanist`ライブラリのSwipe Refreshは非推奨
- 現時点でmaterial3公式のPullToRefreshはリリースされていない

## 2.ViewModelの作成

```NumberViewModel.kt
class NumberViewModel : ViewModel() {
    private val _numbers = MutableStateFlow<List<Int>>(listOf())
    val numbers: StateFlow<List<Int>> = _numbers

    private val _isRefreshing = MutableStateFlow(false)
    val isRefreshing: StateFlow<Boolean> = _isRefreshing

    init {
        initializeNumbers()
    }

    //初期化時
    private fun initializeNumbers() {
        updateNumbers()
    }

    //PullToRefresh時
    fun refreshNumbers() {
        updateNumbers(isRefreshing = true)
    }

    //共通処理
    private fun updateNumbers(isRefreshing: Boolean = false) {
        viewModelScope.launch {
            if (isRefreshing) {
                _isRefreshing.emit(true)
            }

            val newList = List(5) { Random.nextInt(100) }
            _numbers.emit(newList)
            delay(1000)

            _isRefreshing.emit(false)
        }
    }
}
```

このViewModelでは、リストデータとPullToRefreshの状態の管理を担当します。

今回は初回読み込み時とPullToRefresh時とで処理を分岐させていますが、共通処理は`updateNumbers()`でまとめています。

## 3.UIとの繋ぎ込み

```Compose.kt
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwipeRefreshSample(
    modifier: Modifier = Modifier,
    viewModel: NumberViewModel = viewModel()
) {
    val numbers by viewModel.numbers.collectAsState()
    val isRefreshing by viewModel.isRefreshing.collectAsState()

    val state = rememberPullRefreshState(
        refreshing = isRefreshing,
        onRefresh = { viewModel.refreshNumbers() }
    )

    Box(modifier = modifier.pullRefresh(state)) {
        LazyColumn(
            modifier = Modifier.fillMaxWidth()
        ) {
            items(numbers) { number ->
                Text(
                    text = "Number: $number",
                    fontSize = 20.sp,
                    modifier = Modifier.padding(bottom = 10.dp),
                )
            }
        }

        PullRefreshIndicator(
            refreshing = isRefreshing,
            state = state,
            modifier = Modifier.align(Alignment.TopCenter)
        )
    }
}
```

このComposable関数では、上記で作成したViewModelをもとにPullToRefreshによって毎回数字リストをランダムに更新して表示しています。

対象レイアウトを`Box(Modifier.pullRefresh(state))`で囲み、PullToRefresh機能を統合します。pullRefresh()の引数には、`rememberPullRefreshState`関数を用いて更新状態を管理するStateを入れます。

また、`PullRefreshIndicator`で更新中のインジケータ（くるくる回る矢印）の表示を制御します。関数の第一引数には先ほどのState、第二引数にはViewModelで定義した更新状態を入れます。

# サンプルコード
https://github.com/seiyaleb/PullToRefreshTrial

# おわりに
今回はJetpack Composeを使って、PullToRefresh機能を備えたリスト表示の実装手順を簡単に整理していきました。「意外と簡単じゃん」って感じた方も少なくないと思います。

要は、**表示データと更新状態を管理するViewModelを元にComposeに落とし込めば楽勝**なのです。PullToRefresh自体は`Modifier`で指定するので、馴染み深いかと思います。

この記事がComposeで更新機能を作る方に少しでも役立つと嬉しいです。ありがとうございました😎

# 参考サイト
https://medium.com/google-developer-experts/effortlessly-add-pull-to-refresh-to-your-android-app-with-jetpack-compose-4c8b218a9beb

https://qiita.com/Nabe1216/items/eba345e59fae092ed821

https://stackoverflow.com/questions/75683184/jetpack-compose-material3-pull-to-refresh-functionality

# 参考にしたChatGPTの会話

https://chat.openai.com/share/03ff230f-15f9-4fdc-bffb-a222f04edff8