tools:composableNameのススメ

なんかあんまり知られてなさそうだなと思ったので。

## `tools:composableName`

Jetpack Compose は Android View から段階的に移行することが可能です。
現在も段階移行中のプロジェクトは多いと思いますが(弊社ももれなく移行中です)、XML に `ComposeView` や `AbstractComposeView` を継承した CustomView を置くと、Layout Editor 上で表示されず、サイズと見た目がよくわからない状態になってしまうことがあると思います。

![スクリーンショット 2025-03-21 18.42.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/721314/b78460cf-2807-4a1a-90b8-3dba2da2da41.png)

わからない…。

![スクリーンショット 2025-03-21 18.43.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/721314/1e810a1f-eb43-41bf-afc1-92bc33141bac.png)

`ComposeView` には `onCreate` などのタイミングで `setContent` をすることによって上記のような Compose を渡すことができます。Compose 側では`@Preview`を使えば確認できますが、画面全体のバランス感はわかりにくいですね。

そこでおすすめなのが `tools:composableName` です。

`tools:composableName` には任意の Composable関数 を設定することができ、Layout Editor 上でも描画されるようになります。
そのため、逐一ビルドしなくても、Android View 上における Compose のレイアウトが確認でき、マージンなど細かい調整もしやすくなります。


```diff_xml:fragment_sample.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
+        tools:composableName="com.yasuworks.apps.sample.ui.component.BombButtonKt.BombButton"
        />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/description"
        android:textSize="20sp"
        />

</LinearLayout>
```

プレビュー表示したいViewに対して`tools:composableName`を指定してあげます。`BombBotton.kt`ファイルの場合は`BombButtonKt`になることに注意。

![スクリーンショット 2025-03-21 18.44.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/721314/12975241-5be9-4d98-bc98-e36b1981e59e.png)

無事表示されました。

ところがどっこい、引数を設定したいケースがあります。
(というより、基本そういうケースのほうが多いですね。)

```diff_kotlin:BombButton.kt
package com.yasuworks.apps.sample.ui.component

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp

@Composable
fun BombButton(
+    label: String,
    onClick: () -> Unit = {}
) {
    Button(onClick = onClick) {
        Icon(
            imageVector = Icons.Default.Warning,
            contentDescription = "Bomb"
        )
        Text(
-            text = "Bomb!",
+            text = label,
            fontSize = 20.sp
        )
    }
}
```

上記のような 引数が必須なComposable関数の場合、`tools:composableName`を指定してもエラーになってしまいます。この場合は、プレビュー用に`@Preview`関数を作ってあげて、それを指定すればOKです。

```kotlin:BombButton.kt
@Composable
@Preview
fun BombButtonPreview() {
    BombButton(label = "爆発!")
}
``` 

```diff_xml:fragment_sample.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
-        tools:composableName="com.yasuworks.apps.sample.ui.component.BombButtonKt.BombButton"
+        tools:composableName="com.yasuworks.apps.sample.ui.component.BombButtonKt.BombButtonPreview"
        />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/description"
        android:textSize="20sp"
        />

</LinearLayout>
```

![スクリーンショット 2025-03-21 18.51.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/721314/ef84b6a4-125b-4320-96cf-7690678d2c49.png)

無事表示されました。
大抵の場合 Composable関数とプレビューはセットで作っていると思うので、基本運用として xml側でも確認できるようにしたい場合はプレビュー用の関数を指定してあげるのがいいですね。

## 別解: AbstractComposeView

Compose と Android View を併用する場合、`AbstractComposeView` を使う方法もあります。また、その場合 `tools:composableName` を使う必要はなくなります。また、isInEditMode のフラグを使えば、XML Preview 用に任意の値をセットしておくことも可能です。

```kotlin:BombButtonView.kt
class BombButtonView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0,
) : AbstractComposeView(context, attrs, defStyle) {
    var label by mutableStateOf<String>("")
    var onClick by mutableStateOf({})

    init {
        if (isInEditMode) { label = "BAKUHATSU!" }
    }

    @Composable
    override fun Content() {
        BombButton(
            label = label,
            onClick = onClick
        )
    }
}
```


```xml:fragment_sample.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">

    <com.yasuworks.apps.sample.ui.component.BombButtonView
        android:id="@+id/compose_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/description"
        android:textSize="20sp"
        />

</LinearLayout>
```

バッジやボタンなど様々な画面で使い回す View が多いので、小さいUIコンポーネントはどちらかというと AbstractComposeView を継承して作ることが多いです。

実際のところ`tools:composableName`を使用するケースとしては、`RecylerView`を`LazyList`などで置き換えた場合など、ボタンなどより大きい粒度のUIコンポーネントのプレビューをしたいときに使うことが多いですね。

```kotlin:BombButton.kt
@Composable
fun BombButtonList(labels: List<String>) {
    LazyColumn {
        items(labels) { label ->
            BombButton(label = label)
        }
    }
}

@Composable
@Preview
fun BombButtonListPreview() {
    BombButtonList(labels = listOf("ボタンA", "ボタンB", "ボタンC", "ボタンD"))
}
```

```xml:fragment_sample.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:composableName="com.yasuworks.apps.sample.ui.component.BombButtonKt.BombButtonListPreview"
        />

</LinearLayout>
```

![スクリーンショット 2025-03-22 17.35.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/721314/c7968f89-1940-4c81-bdb1-a614740b06c1.png)

こんな感じに表示されます。つまり、Compose関数さえ書いてしまえば基本なんでも Layout Editor 側でプレビューできてしまいます。XML派の人も安心です。

## さいごに

実は公式ドキュメントのこちらを読めばすべて書いてあります。

https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views?hl=ja#composable-layout-editor-preview

Composeはやっぱり書いてい楽しいですね。よきComposeライフを！