普通にアプリの中でも使われる概念で、Jetpack Composeのサンプルアプリのコードや、内部のコードを少しでも読むとすぐに出てくる概念で、知らないと混乱すると思うので、理解しておいたほうが良さそうです。
以下の動画でも登場しました。以下の動画を見てもらってもいいですし、このQiitaでサラッと確認したい人は以下をどうぞ。
https://youtu.be/DDd6IOlH3io?t=445
アプリの中で共有したい色などを渡していきたいときはどうすればよいでしょうか? (この場合通常はMaterialThemeを使うことで解決できますが、このように共有したいものはアプリを作る中で出てきます。 参考: https://github.com/android/compose-samples/search?q=ambient&unscoped_q=ambient )
通常Composeのデータフローは上から下に流れていくので、一応、以下のように関数で渡していくことができます。
class NewsAppPalette(
val primary = ...,
val onPrimary = ...,
)
@Composable
fun NewsApp() {
ArticleList(
articles = ...,
// ** 引数でcolorを渡していっている **
colorPalette = NewsAppPalette(
primary = ...
onPrimary = ...
),
)
}
@Composable
fun ArticleList(articles: List<Article>, colorPalette: NewsAppPalette) {
Row() {
articles.forEach { article ->
Article(article, colors)
}
}
}
fun ArticleItem(article: Article, colorPalette: NewsAppPalette) {
Text(
text = article.title
textColor = colorPalette.onPrimary
)
}
が、こういうふうに渡していくのはけっこう大変ですよね。。??
Jetpack Composeではこれの代わりにambientを提供します。
サービスロケーターのように名前をつけたオブジェクトを作ってそれを使えるようにします。
これはJetpack Composeの MaterialTheme
で使われているのと同じ仕組みです。
提供されているComposeのスコープ以下でその変数が利用可能になります。
ambientの利用方法
ambientの宣言
ambientでデフォルトで配布されるものをブロックの中に書きます。 ambientOf<NewsAppPalette>()
のように 書かなくても宣言できます。
val NewsAppColors = ambientOf<NewsAppPalette> {
LightColorPalette
}
ambientの変数の配布。
注意が必要なのが、このブロックの中とそこから呼び出されるComposable functionの中でのみ利用可能になるということです。
Prividers(NewsAppColors privides LightColorPalette) {
// この中でambientの要素を使える
}
ambientの変数の使い方
NewsAppColors.current
コードから理解するambientの利用方法
コード的には以下になります。このように引数で渡していかなくても、利用することができています。
val NewsAppColors = ambientOf<NewsAppPalette> {
LightColorPalette
}
@Composable
fun NewsApp() {
// ** ここでambientをProvideする **
Prividers(NewsAppColors privides LightColorPalette) {
ArticleList(
// ** 引数でcolorを渡していかなくて良くなっている **
articles = ...
)
}
}
@Composable
fun ArticleList(articles: List<Article>) {
...
Article(article)
...
}
fun ArticleItem(article: Article) {
Text(
text = article.title
textColor = NewsAppColors.current.colors.onPrimary
)
}
(配布されているところの色を変えてわかりやすくしています。)
配布される値の変更
Provideをもう一度することで、そのスコープ内でProvideする値を変えることができます。
(配布されているところの色を変えてわかりやすくしています。)
@Composable
fun NewsApp() {
// ** ここでambientをProvideする **
Prividers(NewsAppColors privides LightColorPalette) {
ArticleList(
articles = ...
)
}
}
@Composable
fun ArticleList(articles: List<Article>) {
...
// ** ここでまた別のDarkColorPaletteをProvideする **
Prividers(NewsAppColors privides DarkColorPalette) {
Row() {
articles.forEach { article ->
Article(article, colors)
}
}
...
}
fun ArticleItem(article: Article) {
Text(
text = article.title
textColor = NewsAppColors.current.colors.onPrimary
)
}
少しだけ効率的に利用する
以下のように NewsAppColors.current
で利用させるより、そのAmbientへのアクセスをラップオブジェクトを作ってあげるパターンがJetpack Compose内ではよく利用されるようです。
object NewsAppTheme {
@Composable
val colors: NewsAppPalette
get() = NewsAppColors.current
...
}
fun ArticleItem(article: Article) {
Text(
text = article.title
textColor = NewsAppTheme.colors.onPrimary
)
}
ambientが提供されていないときにエラーにする
サンプルでは以下のようなコードもありました。エラーがわかりやすくなりますね。
https://github.com/android/compose-samples/blob/1b05455b191ad32dd1f9401771a55a6d6c1abc2a/Owl/app/src/main/java/com/example/owl/ui/utils/Navigation.kt#L107
internal val BackDispatcherAmbient = staticAmbientOf<OnBackPressedDispatcher> {
error("No Back Dispatcher provided")
}
まとめ
これがわかるとかなりの部分のUI周辺のJetpack Composeのコードが読めるようになるので、内部への理解も深まります!
ちょっとはずれますが、 redditでは tree-local value
(ツリーローカルな値) という説明になっており、しっくりきます。