0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LocalSnackbarHostState: CompositionLocalによるSnackbarHostStateの管理方法

0
Last updated at Posted at 2025-06-28

モバイルアプリにおいてユーザーへの情報伝達は非常に重要です。その中でも、短時間でユーザーにフィードバックを伝える際に便利なUI要素としてSnackbarがあります。

この記事では、ComposeにおけるSnackbarの基本から、画面などのスコープを意識したSnackbarの状態・表示を CompositionLocal によりうまく扱う方法を紹介します。

Snackbarとは

Snackbarは、画面の下部に一時的に表示されるUI要素で、ユーザーへの短いメッセージやアクション可能なフィードバックを提供します。

Androidには類似のUI要素としてToastがあります。SnackbarはToastと以下のような点で異なります。表示期間・アクション・デザインといった機能性が注目されることが多いですが、この記事では「スコープ」に着目します。

Snackbarは表示する要因になった画面やアプリ内で表示されます。そのため、画面遷移などによって表示されなくなる必要があります。Snackbarを扱うとき、どの画面に表示するかは意識する必要があります。

Snackbar Toast
表示期間 自動消滅・手動消滅 自動消滅
アクション ボタンを配置可能 不可
デザイン Material Designに準拠、自由度が高い 変更不可
スコープ 画面・アプリ(特定機能に対して) デバイス全体(システムに対して)
表示例 snackbar.gif toast.gif

ComposeにおけるSnackbar

ComposeでSnackbarを表示するには、SnackbarHostSnackbarHostState を使用します。

SnackbarHost はSnackbarの表示領域を定義するComposable関数です。通常、Scaffold(Material Designによる基本的な画面レイアウト)の snackbarHost に配置して利用します。これにより、Snackbarが画面の適切な位置に表示されるようになります。

Scaffold(
    snackbarHost = {
        SnackbarHost(
            hostState = snackbarHostState,
        )
    },
    // ...
)

そして、実際にSnackbarの表示・非表示やメッセージ内容などの制御を行うのが SnackbarHostState です。これはUIの状態を保持するステートホルダーであり、Composable関数から利用する場合は remember を使って状態を記憶させます。

val snackbarHostState = remember { SnackbarHostState() }

Snackbarを表示するには、SnackbarHostStateshowSnackbar を呼び出します。このメソッドはsuspend関数であるため、コルーチン内で呼び出す必要があります。

val coroutineScope = rememberCoroutineScope()
Button(
    onClick = {
        coroutineScope.launch {
            snackbarHostState.showSnackbar(message = "message")
        }
    },
) {
    Text(text = "Show Snackbar")
}

CompositionLocal とは

Snackbarは画面などの特定のスコープを意識して表示を切り替える必要があります。そのため、画面ごとに SnackbarHostSnackbarHostState が必要になるのが一般的です。しかし、アプリ内で深くネストされた子・孫コンポーネントからSnackbarを表示したい場合、SnackbarHostState を親コンポーネントから引数として順々に渡していくバケツリレー(Props Drilling)が発生し、コードの保守性が低下することがあります。

そこで、CompositionLocal が役立ちます。CompositionLocal は、Composeのcomposition を通じてデータやサービスを暗黙的に「渡す」ための仕組みです。通常の引数によるデータ渡しとは異なり、階層の深い部分にあるComposable関数でも、親から明示的に引数として渡されなくても、特定の CompositionLocal で提供された値にアクセスできるようになります。

これにより、「この画面ではこの SnackbarHostState を利用する」といった、スコープに紐づいたSnackbarの管理が可能になります。子コンポーネントは、自動的にそのスコープに合わせた SnackbarHostState を利用できるため、コードがよりすっきりと記述できます。

CompositionLocal を使ってSnackbarを管理する具体的な例を紹介します。まず、SnackbarHostState を保持する CompositionLocal を定義します。

val LocalSnackbarHostState = staticCompositionLocalOf { SnackbarHostState() }

Scaffold に対して

Scaffold を含むComposable関数で LocalSnackbarHostStateを提供し、その下位のComposable 関数から SnackbarHostState を利用できるようにします。

このように MyScaffold を実装しておけば、それ以下のComposable関数からでも LocalSnackbarHostState.current を使って SnackbarHostState を取得し、その画面にSnackbarを表示できます。つまり、この Something は、MyScaffold 内に配置されていれば、MyScaffold にSnackbarを表示します。

@Composable
fun MyScaffold(
    snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
    // ...
) {
    CompositionLocalProvider(
        LocalSnackbarHostState provides snackbarHostState,
    ) {
        Scaffold(
            snackbarHost = {
                SnackbarHost(hostState = snackbarHostState)
            },
            // ...
        )
    }
}
@Composable
fun Something() {
    val snackbarHostState = LocalSnackbarHostState.current
    val coroutineScope = rememberCoroutineScope()
    Button(
        onClick = {
            coroutineScope.launch {
                snackbarHostState.showSnackbar(message = "Message")
            }
        },
    ) {
        Text(text = "Show Snackbar")
    }
}

NavGraphBuilder.composable に対して

Scaffold を利用しない場合や、Scaffold よりも広い範囲でSnackbarのスコープを管理したい場合は、ナビゲーショングラフ定義時に SnackbarHostState を提供すると良いでしょう。この場合も同様に CompositionLocalProvider を利用できます。これにより、特定の画面(ルート)に遷移したときに、その画面でのみ有効な SnackbarHostStateCompositionLocal として提供されます。

このように NavGraphBuilder.myComposable を実装した場合、LocalSnackbarHostState.current を使って同様の SnackbarHostState を取得できます。

inline fun <reified T : Any> NavGraphBuilder.myComposable(
    // ...
    noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
) = composable<T>(/* ... */) { entry ->
    CompositionLocalProvider(
        LocalSnackbarHostState provides remember { SnackbarHostState() },
    ) {
        content(entry)
    }
}

他にも便利なこと

CompositionLocal を使うと、コンポーネントから直接Snackbarを操作できるようになるだけでなく、よりアプリケーション全体で利用可能な共通ロジックを実装する際に便利です。例えば、以下のようにURIを開く処理でエラーが発生した場合にSnackbar を表示する独自の UriHandler を定義できます。

この rememberMyUriHandler は、LocalSnackbarHostState に依存しています。もし、LocalSnackbarHostStateCompositionLocal として提供されていなければ、これを呼び出すたびに SnackbarHostState を引数で渡す必要があり、使い勝手が悪いです。

@Composable
fun rememberMyUriHandler(): UriHandler {
    val uriHandler = LocalUriHandler.current
    val snackbarHostState = LocalSnackbarHostState.current
    val coroutineScope = rememberCoroutineScope()
    val onFailure: State<(uri: String) -> Unit> = rememberUpdatedState { uri ->
        coroutineScope.launch {
            snackbarHostState.showSnackbar(message = "Can't open $uri.")
        }
    }
    return remember(uriHandler, onFailure) {
        object : UriHandler {
            override fun openUri(uri: String) {
                try {
                    uriHandler.openUri(uri)
                } catch (e: IllegalArgumentException) {
                    onFailure.value(uri)
                }
            }
        }
    }
}

まとめ

この記事では、ComposeにおけるSnackbarの基本から、SnackbarHostStateCompositionLocal を使って管理する方法について紹介しました。CompositionLocal を使うことで、Snackbarのスコープをうまく扱うことができ、以下のようなメリットがあります。

  • スコープ範囲の明確化: 子コンポーネントが、そのスコープに紐づくSnackbarを表示できるようになる
  • コードの集約: SnackbarHostState を扱うためのコードが1箇所に集約され、他のコンポーネントは表示のための実装に専念しやすくなる
  • バケツリレーの解消: SnackbarHostState を引数で渡す必要がなくなるため、コードの可読性と保守性が向上する

参考文献

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?