Redwood - 通过Kotlin跨平台构建响应式多平台UI

1,064 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Redwood

Redwood是一个开源库, 使用Kotlin构建响应式Android, iOS, 和web UI.

Redwood当前处于开发之中并没有准备好对外使用.

响度式UI

Android和iOS UI框架提供用户交互为模型作为‘可变视图树’或者文档对象模型(DOM). 要使用可变对象树抽象构建应用, 编程人员执行2个离散步骤:

  • 构建静态视图树.  在Android中, 构建静态视图树的传统工具是XML布局, 尽管我们已经使用Contour做了相同的很酷的工作, 使用Kotlin lambda来构建视图.
  • 让视图动起来.  视图树应该随着用户交互(像点击按钮)和外部事件(像数据加载)进行变化. 程序变更视图树以表示当前应用状态. 一些变更立即改变了屏幕上的UI; 其它的则从旧状态到新状态顺滑地执行动画.

React使新的编程模型流行起来, 即响应式UI. 有了响应式UI, 编程人员写了render()函数, 该函数接收了应用状态并返回了视图树. 框架用初始应用状态调用了这个函数, 并且之后每一次应用状态变更都会再次调用. 该框架分析视图树对之间的差异并更新显示, 包括在适当的地方设置过渡动画.

在React中, render函数返回的视图树叫做虚拟DOM, 并且在它有一个屏幕上的等价物叫真实DOM. 虚拟DOM是一个简单JS值对象树; 真实DOM是一个实时浏览器HTML组件树. 创建并遍历数以千计的虚拟DOM对象是很快的; 创建数以千计的HTML组件却并不然! 所以, 虚拟DOM优化便是React做的魔法.

Compose

Jetpack Compose是Android平台响应式UI模型的一个实现. 它使用了实现技术进一步优化了响应式编程模型. 这通过2个互补模块来实现:

  • Compose编译器 是一个Kotlin编译器插件, 支持函数的部分重估. 编程人员依然写渲染函数将应用状态转化为视图树. 编译器重写了这个函数追踪哪个输入产生哪个输出. 当应用状态输入变更的时候, 它只评估对于生成对应视图树变更必要的东西.
  • Compose UI 是新的Android UI组件集合, 设计出来与Compose编译器一起工作. 它致函了Android视图系统长期以来的技术债.

Compose编译器重写的Kotlin函数叫做可重组函数. 可重组函数的部分重估叫做重组.

注意: Compose编译器没有Compose UI也可以使用. 比如, compose服务器侧在服务器上渲染了HTML组件, 这些组件通过WebSocket发送到了浏览器上.

设计系统

在Cash应用上我们使用了一个设计系统. 它详细指定了UI并且命名了它的元素:

  • 标准颜色, 字体, 图标和维度的名字
  • 使用上述名字命名了文本块
  • 命名了控制, 比如标准确认框, 按钮, 和对话框

设计系统有助于编程人员和设计人员之间的协作. 它也增加了应用内和跨平台的一致性.

什么是Redwood?

Redwood集成了Compose编译器, 设计系统, 和平台相关的显示集合. 每一个Redwood项目由3部分实现:

  • 设计系统.  Redwood包含了一个叫做‘Sunspot’的设计系统例子. 大多数应用应该自定义这个设计系统以匹配他们的产品需求.
  • UI平台显示.  显示在屏幕上绘制设计系统的像素. 显示能够为任意UI平台实现. Redwood对Android, iOS和web平台引入了Sunspot显示例子.
  • 可组合函数. 这是客户端的逻辑, 接收应用状态并返回设计系统的元素. 这些跟MVP系统中的Presenter有相似的职责.

为什么是Redwood?

我们渴望开始写响应式UI! 但是我们不愿意继续跨iOS, Android和web平台重复代码. 尤其是, 我们不喜欢支持多个平台会降低我们的整体灵活性.

我们要想走缓慢的原生UI开发进程的捷径. Android UI上面的迭代要求缓慢的编译步骤和adb install步骤. 有了Redwood, 我们希望使用web作为我们开发目标, 尤其是我们在可组合函数变更上迭代时.

我们要想一个选项来修改应用的行为, 却不想等待用户更新他们的应用. 有了Kotlin/JS我们也许可以在应用开启时更新我们的可组合函数, 然后在JS虚拟机上运行. 我们甚至能够使用WebAssembly在没有性能损失的情况下达成这个目标.

为什么不是React Native?

React Native的确不可抗拒. 但是我们也已经读到集成RN到已有项目和小组的困难.

Redwood是一个包, 而非一个框架.  它设计用来增量适配并且低风险和与已有Android项目集成. 在iOS或者web应用中使用Redwood风险更大! 我们使用Kotlin移动跨平台有过好的体验, 并且期待Redwood有相似的输出.

代码例子

我们首先将设计系统表示为一组Kotlin数据类. Redwood将使用这些类为显示和可组合函数生成类型安全的API.

@Widget(1)
data class SunspotText(
  @Property(1) val text: String?,
  @Property(2) @Default(""black"") val color: String,
)

@Widget(2)
data class SunspotButton(
  @Property(1) val text: String?,
  @Property(2) @Default("true") val enabled: Boolean,
  @Property(3) val onClick: () -> Unit,
)

显示通过使用原生UI组件实现了设计系统.

class AndroidSunspotText(
  override val value: TextView,
) : SunspotText<View> {
  override fun text(text: String?) {
    value.text = text
  }

  override fun color(color: String) {
    value.setTextColor(Color.parseColor(color))
  }
}

可组合函数将应用状态渲染进设计系统. 这将使用Compose API的一些特性, 如remember()等.

@Composable
fun Counter(value: Int = 0) {
  var count by remember { mutableStateOf(value) }

  SunspotButton("-1", onClick = { count-- })
  SunspotText(count.toString())
  SunspotButton("+1", onClick = { count++ })
}