Jetpack Compose使用Ambient注入、获取全局变量

Jetpack Compose中没有Class Component的概念,所以的组件都是Compose Function,当state变化时通过重复执行Compose Function刷新UI。

理想情况下Compose Function应该以纯函数的形式运行,函数的执行结果只依赖入参。

@Composable
fun MyView(someData: Data) {
  ...
}

如上,someData可能是存放在上级的某个state,需要所有的子组件参照。

当组件嵌套过深时,各级Compose Function都要在函数签名中增加此参数;而且当这样的参数多起来后会成为一种灾难。因此Compose提供了Ambient,可以进行实例对象的注入和获取,避免依赖参数传递

1. Ambient


通过ambientOf创建一个Ambient

val exampleViewModelAmbient = ambientOf<ExampleViewModel>()

在父组件中通过Ambient注入实例,子组件通过Ambient获取实例:

val exampleViewModelAmbient = ambientOf<ExampleViewModel>()

@Composable
fun ExampleApp() {
  val viewModel = ExampleViewModel()

  Providers(exampleViewModelAmbient provides viewModel) {
    MyView()
  }
}

@Composable
private fun MyView() {
  val viewModel = exampleViewModelAmbient.current
  ...
}

如上,一个基本的注入和使用完成了,代码很简单。


2. 实现原理


简单了解一下内部实现:

注入实例

Providers(exampleViewModelAmbient provides viewModel)

其中provides方法是一个中缀,返回一个ProvideValue

abstract class ProvidableAmbient<T> internal constructor(defaultFactory: (() -> T)?) :
    Ambient<T> (defaultFactory) {
    infix fun provides(value: T) = ProvidedValue(this, value)
}

ProvideValue非常简单,其实是一个Ambient和实例的关联组合

class ProvidedValue<T> internal constructor(val ambient: Ambient<T>, val value: T)

Providers也非常简单,三件事:

  1. ProvideValue入栈
  2. 执行其内部的Composable函数之后
  3. ProvideValue出栈
@Composable
fun Providers(vararg values: ProvidedValue<*>, children: @Composable() () -> Unit) {
    currentComposer.startProviders(values)
    children()
    currentComposer.endProviders()
}

当前栈上有一个AmbientMap,入栈相当于将ProvideValue存入Map
出栈相当于从AmbientMap中删除

获取实例

val viewModel = exampleViewModelAmbient.current

通过Ambient获取实例,current是个Custom Getter,遍历AmbientMap上的ProvideValue,最终获取实例

Ambient本身不持有value,只是作为一个Key来索引Map中真正的实例。相对于普通的Key,Ambient声明时的泛型,可以让他类型安全的获取实例

sealed class Ambient<T> constructor(defaultFactory: (() -> T)? = null) {
    @Composable
    inline val current: T get() = currentComposer.consume(this)
}

分层管理

为什么要通过Providers函数实现这样的Stack逻辑呢?,用一个全局Map作为IoC容器不就可以了吗?

这样设计的目的是为了实现分Scope管理

Ambients by their nature are hierarchical. They make sense when the value of the ambient needs to be scoped to a particular sub-hierarchy of the composition.

Composable函数的执行相当于一个树的DFS,每一层都有自己的AmbientMap,管理当前的实例,同一个Ambient在不同Scope可以对应不同实例,如下:

@Composable
fun App() {
  val viewModel = AppViewModel()
  Providers(exampleViewModelAmbient provides viewModel) {
	Parent()
  }
}

@Composable
fun Parent() {
  Providers(exampleViewModelAmbient provides ParentViewModel()
) {
	Child()
  }
  //获取的是AppViewModel而非ParentViewModel
  viewModel = exampleViewModelAmbient.current
}

@Composable
private fun Child() {
  // 获取的是ParentViewModel
  val viewModel = exampleViewModelAmbient.current
  ...
}

3. 使用注意


Providers包裹是必须的,否则汇报运行时异常

@Composable
fun ExampleApp() {
  val viewModel = ExampleViewModel()

  Providers(exampleViewModelAmbient provides viewModel) {
    ...
  }

  // runtime error
  MyView()
}

当有个多个Providers方法时,最近的一个有效,因为当前Scope的AmbientMap已经被改写

fun ExampleApp() {
  val viewModel = ExampleViewModel()

  Providers(exampleViewModelAmbient provides viewModel) {

    val viewModel2 = ExampleViewModel()
    Providers(exampleViewModelAmbient provides viewModel2) {
      // 此时获取的是viewModel2
      MyView()
    }
  }
}

最后

Compose的内部使用了很多Ambient子类来传递全局性的环境变量,例如ContextAmbientCoroutineContextAmbien

参考
https://developer.android.com/reference/kotlin/androidx/compose/Ambient

你可能感兴趣的:(Android)