【Jetpack Compose】スロットという考え方

## はじめに


本記事は [with Advent Calendar 2021](https://qiita.com/advent-calendar/2021/with) 6日目の記事です。

こんにちは [with](https://with.is/welcome) でAndroid エンジニアをしている石田です。withでは Jetpack Compose を段階的にプロダクトに導入しています。

本記事では、Jetpack Composeで知っていると便利なスロット(Slot)という考え方を紹介します。

## スロットとは

[スロット](https://www.weblio.jp/content/%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88)は **「投入口」** という意味です。Jetpack ComposeではUI部品を定義して共通化できるのはもちろんのこと、スロットによりUI部品の組み合わせ方を共通化することもできます。

例を見るのが早いと思うので、2つ紹介します。


## 例1: 注文のチェックアウト

こんな画面を作ってくださいと言われたとします。画面を観察すると、セクションごとに「左上のタイトル」、「右上のアクション」、「コンテンツ」の3つの部品の組み合わせで構成されていることが分かります。

| 画面 | 構造 |
| :--: | :----------: |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/33bf5022-cc01-44e2-114f-8eacb8d07e01.png" width="300" /> | ![slot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/d8bd67ca-597a-1693-5ce4-e470b0d51966.png) |

そこで、3つのスロットを考えます。

* Title slot
* Action slot
* Content slot

これらのスロットに何をはめ込むかは呼び出し側の自由です。`title` と `action` は省略できると便利なので デフォルトで空のコンポーザブル関数を渡しておきます。

```kotlin
@Composable
fun SectionTemplate(
    modifier: Modifier = Modifier,
    title: @Composable () -> Unit = {},
    action: @Composable () -> Unit = {},
    content: @Composable () -> Unit
) {
    Surface(
        color = MaterialTheme.colorScheme.secondaryContainer,
        contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
        shape = RoundedCornerShape(32.dp),
    ) {
        Column(
            modifier = Modifier
                .padding(horizontal = 24.dp, vertical = 16.dp)
                .then(modifier),
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            Row(
                horizontalArrangement = Arrangement.spacedBy(16.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Box(
                    modifier = Modifier.weight(1f),
                ) {
                    ProvideTextStyle(
                        MaterialTheme.typography.titleLarge,
                    ) {
                        title()
                    }
                }
                action()
            }
            content()
        }
    }

```

この `SectionTemplate` を使用して `CartSection`　と `PaymentSection` を作成します。

```kotlin
@Composable
fun CartSection() {
    SectionTemplate(
        title = {
            Text(text = "Cart")
        },
        action = {
            TextButton(onClick = {}) {
                Text(text = "See all")
            }
        }
    ) {
        Column(
            verticalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            IntRange(1, 6).forEach {
                Text(text = "Item $it")
            }
            Text(text = "...")
        }
    }
}
```

```kotlin
@Composable
fun PaymentSection() {
    SectionTemplate(
        title = {
            Text(text = "Payment")
        },
        action = {
            IconButton(onClick = {}) {
                Icon(
                    imageVector = Icons.Filled.Info,
                    contentDescription = null,
                )
            }
        }
    ) {
        Column(
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            Text(text = "XXXX-XXXX-XXXX")
            Text(text = "XX/XX")
            Text(text = "XXX")
        }
    }
}
```

## 例2: Scaffold

実は Scaffold にも以下のようなスロットが用意されていて、自由に中身をはめ込めるようになっています。

* Top Bar slot
* Bottom Bar slot
* Floating Action Button slot
* Drawer Content slot
* Content slot

```kotlin
@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    floatingActionButton: @Composable () -> Unit = {},
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    .. 略 ..
    content: @Composable (PaddingValues) -> Unit
) {
  .. 略 ..
}
```

引用元:

[https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt;l=158-177?q=scaffold.kt](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt;l=158-177?q=scaffold.kt)

## まとめ

* ComposeではUI部品を共通化できるだけでなく、スロットによりUI部品の組み合わせ方を共通化することもできます。
* スロットは抽象度が高めになりますが、強力な考え方になるので覚えておくとよいです。
* [動作可能なサンプルコードをGitHubに公開している](https://github.com/maxfie1d/compose-slot/tree/main)ので、併せてご参照ください。