hikage-runtime

Maven CentralMaven metadata URLAndroid Min SDK

这是 Hikage 的运行时依赖,你可以引入它来使用状态管理相关功能。

配置依赖

你可以使用如下方式将此模块添加到你的项目中。

我们推荐你优先参考 hikage-bom 使用 BOM 统一管理版本。

在你的项目 gradle/libs.versions.toml 中添加依赖。

[versions]
hikage-runtime = "<version>"

[libraries]
hikage-runtime = { module = "com.highcapable.hikage:hikage-runtime", version.ref = "hikage-runtime" }

在你的项目 build.gradle.kts 中配置依赖。

implementation(libs.hikage.runtime)

请将 <version> 修改为此文档顶部显示的版本。

传统方式

在你的项目 build.gradle.kts 中配置依赖。

implementation("com.highcapable.hikage:hikage-runtime:<version>")

请将 <version> 修改为此文档顶部显示的版本。

功能介绍

你可以 点击这里在新窗口中打开 查看 KDoc。

状态管理

Hikage 为基于 View 的布局提供了一套轻量状态管理方案。

不同于 Jetpack Compose 的重组 (Recompose),Hikage 不会在状态变化时重新构建布局树。 状态变化会通过监听回调生效,并直接修改已经存在的 View 实例。

Hikage 提供两种状态类型:

  • NonNullState 持有非空值
  • NullableState 持有可空值

在布局组件中推荐直接使用 View.setState(...) 绑定状态。 它会在 View attach 到窗口时注册监听,在 detach 时自动取消监听,再次 attach 时会重新注册并同步最新状态。

如果你需要直接监听状态变化,observe(...) 会返回一个 StateSubscription。 你可以通过调用 cancel() 主动取消监听。

示例如下

val textState = mutableStateOf("Hello, World!")
val drawableState = mutableStateOfNull<Drawable>()

var text by textState
var drawable by drawableState

LinearLayout(
    lparams = LayoutParams(matchParent = true),
    init = {
        orientation = LinearLayout.VERTICAL
    }
) {
    TextView {
        setState(textState) {
            this.text = it
        }
    }
    ImageView {
        setState(drawableState) {
            setImageDrawable(it)
        }
    }
    Button {
        this.text = "Click Me!"
        setOnClickListener {
            text = "Hello, Hikage!"
            drawable = drawableResource(R.drawable.ic_my_drawable)
        }
    }
}

在上面的示例中,mutableStateOf 会创建非空状态,mutableStateOfNull 会创建可空状态。

点击按钮后,已经存在的 TextViewImageView 会通过状态回调被更新。

注意

直接使用 state.observe(...),或在非 View 对象上使用 setState(...) 时,监听会作为长生命周期订阅存在,不会自动跟随 Viewdetach 释放。

如果监听目标可能早于状态对象销毁,请保存返回的 StateSubscription 并在不再需要时调用 cancel()

生命周期状态

ViewModel 场景中,我们推荐的模型是:

ViewModel 持有状态
Hikage 构建 View 树
hikage-runtime 将 Flow/LiveData 绑定到 View 修改

com.highcapable.hikage.runtime.lifecycle 提供了针对 StateFlowFlowLiveData 和 effect flow 的生命周期感知适配。

View 为接收者的 API 会自动从 View 树中查找 LifecycleOwner。如果当前没有可用的所有者对象,请显式传入 lifecycleOwner

所有生命周期感知 API 默认会在生命周期达到 Lifecycle.State.STARTED 时进行收集或监听。你可以通过 minActiveState 自定义这个行为。

StateFlow

StateFlow 是现代 ViewModel 中最常用的状态持有者。它拥有当前值,因此 setState(...) 会立即应用 state.value,并在生命周期内继续收集更新。

示例如下

data class LoginUiState(
    val username: String = "",
    val password: String = ""
) {
    val canSubmit get() = username.isNotBlank() && password.isNotBlank()
}

class LoginViewModel : ViewModel() {

    private val mutableUiState = MutableStateFlow(LoginUiState())

    val uiState = mutableUiState.asStateFlow()

    fun updateUsername(value: String) {
        mutableUiState.update { it.copy(username = value) }
    }
}

在 Hikage 布局中使用。

示例如下

TextInputEditText {
    doOnTextChanged { text, _, _, _ ->
        viewModel.updateUsername(text.toString())
    }
}

Button {
    setState(viewModel.uiState) {
        isEnabled = it.canSubmit
    }
}

Flow

对于普通 Flow,如果你需要在第一次发送前提供初始值,可以使用 setState(flow, initialValue)

示例如下

TextView {
    setState(
        flow = viewModel.runtimeTicker,
        initialValue = 0
    ) {
        text = "Flow 计时:$it 秒"
    }
}

如果你不需要初始值,可以使用 collectState(...)

示例如下

TextView {
    collectState(viewModel.runtimeTicker) {
        text = "collectState 镜像 $it"
    }
}

LiveData

Hikage 同样支持 LiveData,如果 LiveData 已经拥有初始化值,Hikage 会先应用当前值,再开始监听。

TextView {
    setState(viewModel.compatibilityStatus) {
        text = it
    }
}

Effect

对于 Toast、导航、弹窗显示这类一次性 UI 事件,请使用 collectEffect(...)

与状态收集不同,effect 会逐个收集并处理。

示例如下

collectEffect(viewModel.effects, lifecycleOwner = this) { effect ->
    when (effect) {
        is LoginEffect.Toast -> toast(effect.message)
        LoginEffect.NavigateBack -> finish()
    }
}

小提示

collectEffect(...) 接受任意 Flow,包括 SharedFlowChannel.receiveAsFlow()

注意

当接收者不是 View,或当前 View 所在的 View 树中没有 LifecycleOwner 时,请显式传入 lifecycleOwner

示例如下

setState(
    state = viewModel.uiState,
    lifecycleOwner = this
) {
    // 在这里更新非 View 对象的状态
}

所有生命周期感知 API 都会返回 StateSubscription,你可以在需要时手动取消订阅。