Jetpack Composeで頻出する"ambient"とは

普通にアプリの中でも使われる概念で、Jetpack Composeのサンプルアプリのコードや、内部のコードを少しでも読むとすぐに出てくる概念で、知らないと混乱すると思うので、理解しておいたほうが良さそうです。

以下の動画でも登場しました。以下の動画を見てもらってもいいですし、このQiitaでサラッと確認したい人は以下をどうぞ。
https://youtu.be/DDd6IOlH3io?t=445

アプリの中で共有したい**色などを渡していきたいときはどうすればよいでしょうか？** (この場合通常はMaterialThemeを使うことで解決できますが、このように共有したいものはアプリを作る中で出てきます。 参考: https://github.com/android/compose-samples/search?q=ambient&unscoped_q=ambient )
通常Composeのデータフローは上から下に流れていくので、一応、以下のように関数で渡していくことができます。


```kotlin
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
  )
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/619491c2-910a-4d67-df9e-c9d5d3d83bb2.png)


が、こういうふうに渡していくのはけっこう大変ですよね。。？？


Jetpack Composeではこれの代わりにambientを提供します。
サービスロケーターのように名前をつけたオブジェクトを作ってそれを使えるようにします。
これはJetpack Composeの `MaterialTheme` で使われているのと同じ仕組みです。
提供されているComposeのスコープ以下でその変数が利用可能になります。

## ambientの利用方法

ambientの宣言
ambientでデフォルトで配布されるものをブロックの中に書きます。 `ambientOf<NewsAppPalette>()`のように 書かなくても宣言できます。

```kotlin
val NewsAppColors = ambientOf<NewsAppPalette> {
  LightColorPalette
}
```

ambientの変数の配布。
**注意が必要なのが、このブロックの中とそこから呼び出されるComposable functionの中でのみ利用可能になるということです。**

```kotlin
  Prividers(NewsAppColors privides LightColorPalette) {
    // この中でambientの要素を使える
  }
```

ambientの変数の使い方

```kotlin
NewsAppColors.current
```

## コードから理解するambientの利用方法

コード的には以下になります。このように引数で渡していかなくても、利用することができています。

```kotlin
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
  )
}
```

(配布されているところの色を変えてわかりやすくしています。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/b7bc8e1a-752f-e415-5cef-929483e3823d.png)


## 配布される値の変更

Provideをもう一度することで、そのスコープ内でProvideする値を変えることができます。

(配布されているところの色を変えてわかりやすくしています。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/b0eb2130-41b4-782c-0d0a-5f75e51aa935.png)


```kotlin
@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内ではよく利用されるようです。

```kotlin
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

```kotlin
internal val BackDispatcherAmbient = staticAmbientOf<OnBackPressedDispatcher> {
    error("No Back Dispatcher provided")
}
```

# まとめ
これがわかるとかなりの部分のUI周辺のJetpack Composeのコードが読めるようになるので、内部への理解も深まります！
ちょっとはずれますが、 redditでは [`tree-local value` (ツリーローカルな値) という説明](https://www.reddit.com/r/androiddev/comments/idefss/were_on_the_engineering_team_for_android_jetpack/g31yqa2?utm_source=share&utm_medium=web2x&context=3)になっており、しっくりきます。