【Jetpack Compose】Preview をもっと簡単に増やしたい場合（PreviewParameter, Multipreview Annotations）

## はじめに

Preview をより簡単に増やせる方法を学んだので、それらを備忘録としてまとめていこうと思います。


### この記事で分かること

- `@PreviewParameter` の使い方
- Multipreview Annotations の使い方

## 今回 Preview させるもの

単純にメモのリストを表示させるような、非常に簡単なもので試していきたいと思います。`Loading` 時には `CircularProgressIndicator()` を、`Error` 時には `Dialog` を表示させています。

```kotlin
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun ExampleScreen(uiState: ExampleUiState) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = "ExampleScreen") }
            )
        }
    ) {
        if (uiState.memos.isEmpty()) {
            Text(
                text = "まだメモがありません",
                modifier = Modifier.fillMaxSize().padding(top = 40.dp),
                textAlign = TextAlign.Center,
            )
        } else {
            MemoList(memos = uiState.memos)
        }

        if (uiState.isLoading) {
            Column(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                CircularProgressIndicator()
            }
        }

        if (uiState.isError) {
            Dialog(onDismissRequest = {}) {
                Column(
                    modifier = Modifier.size(300.dp, 200.dp).background(Color.White),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text(text = "エラーです", color = Color.Black)
                }
            }
        }
    }
}

@Composable
fun MemoList(memos: List<Memo>) {
    LazyColumn(
        contentPadding = PaddingValues(horizontal = 16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        items(memos) { memo ->
            Text(text = memo.title)
        }
    }
}
```

簡略化のために `Memo` は `title` のみを持たせています。

```kotlin
data class ExampleUiState(
    val memos: List<Memo> = emptyList(),
    val isLoading: Boolean = false,
    val isError: Boolean = false
)

data class Memo(
    val title: String
)
```

### 単純にPreviewをさせる場合
以下のように、uiState の中身だけが異なる Preview が多発します。

```kotlin
// メモがない場合
@Preview
@Composable
fun Preview1() {
    ExampleTheme {
        ExampleScreen(
            uiState = ExampleUiState()
        )
    }
}

// メモがある場合
@Preview
@Composable
fun Preview2() {
    ExampleTheme {
        ExampleScreen(
            uiState = ExampleUiState(
                memos = List(50) { Memo(title = "タイトル No.$it") }
            )
        )
    }
}

// Loading 状態
@Preview
@Composable
fun Preview3() {
    ExampleTheme {
        ExampleScreen(
            uiState = ExampleUiState(
                memos = List(50) { Memo(title = "タイトル No.$it") },
                isLoading = true
            )
        )
    }
}

// Error状態
@Preview
@Composable
fun Preview4() {
    ExampleTheme {
        ExampleScreen(
            uiState = ExampleUiState(
                memos = List(50) { Memo(title = "タイトル No.$it") },
                isError = true
            )
        )
    }
}
```

最初は良くても増えてくると結構面倒になっていきます…。

## `@PreviewParameter` を使用

そんな時に活用するのが `@PreviewParamerter` です。
まずは以下のようなクラスを作成しましょう。好きなだけ `ExampleUiState` を作ることが出来ます。

```kotlin
class FakeExampleUiStateProvider : PreviewParameterProvider<ExampleUiState> {
    override val values = sequenceOf(
        ExampleUiState(),
        ExampleUiState(
            memos = List(50) { Memo(title = "タイトル No.$it") }
        ),
        ExampleUiState(
            memos = List(50) { Memo(title = "タイトル No.$it") },
            isLoading = true
        ),
        ExampleUiState(
            memos = List(50) { Memo(title = "タイトル No.$it") },
            isError = true
        ),
        ExampleUiState(
            memos = List(50) {
                Memo(title = "長いタイトル長いタイトル長いタイトル長いタイトル長いタイトル長いタイトル No.$it")
            }
        )
    )
}
```

そして以下のように `@PreviewParameter` を使うだけで、先ほど作成した `ExampleUiState` を使うことが出来ます。

```kotlin
@Preview
@Composable
fun ExamplePreview(
    @PreviewParameter(FakeExampleUiStateProvider::class) uiState: ExampleUiState // ここ！
) {
    ExampleTheme {
        ExampleScreen(uiState = uiState)
    }
}
```

結果は以下の通りになります。
それぞれの Preview をわざわざ5つ書かなくても `@PreviewParameter` を使用すると簡単に表示出来て便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1630025/98338664-8d94-39ea-f579-298746410c44.png)

これらをさらにLightモード、Nightモードの両方で確認したい場合があると思います。そんな際に便利なのが自分でアノテーションを作成する方法です。

## Multipreview Annotations
以下にある通り、場合によっては使う事が出来ないので注意してください。
> Note: This feature is available starting from Android Studio Dolphin and Jetpack Compose 1.2.0-beta01.（[公式](https://developer.android.com/jetpack/compose/tooling#preview-multipreview)より）

まずは annotation class を作成し、

```kotlin
@Preview(
    name = "light mode",
    group = "ui modes",
    uiMode = UI_MODE_NIGHT_NO
)
@Preview(
    name = "night mode",
    group = "ui modes",
    uiMode = UI_MODE_NIGHT_YES
)
annotation class UiModePreviews
```

このアノテーションを表示したい Preview に付けるだけです。

```kotlin
@UiModePreviews // ここ！
@Composable
fun ExamplePreview(
    @PreviewParameter(FakeExampleUiStateProvider::class)
    uiState: ExampleUiState
) {
    ExampleTheme {
        ExampleScreen(uiState = uiState)
    }
}
```

これでLightモード・Nightモードの両方を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1630025/c7940917-aa7d-eb21-74d2-0422c18559d0.png)

## 参考資料

https://developer.android.com/jetpack/compose/tooling

https://developer.android.com/jetpack/compose/lists

## おわりに

上記の通り、これらを活用すればより簡単に Preview を表示できるので便利です！

ここまで読んでいただき、ありがとうございました！何かありましたら、コメントをお願いいたします。