我见过一些 Jetpack Compose 项目,并且见过两种类型的管理状态,但没有意识到哪一种更好。
例如,我们假设:输入状态。我见过人们在 UI 中管理此状态,使用 Remember 来保存值的状态。
我见过的另一种方法是在 ViewModel 中创建此 mutableState 并从那里存储/使用它。最好的方法是什么?
除了@Thracian的回答。
让我根据我目前的 Jetpack Compose 经验水平来分享我的思考过程。只是免责声明,我仍处于学习曲线中。
IMO,不存在“最好”这样的东西,我们领域的事物在不断发展,今天被认为是“最好”的东西明天可能会过时,但是有一些被社区“推荐”、批准和采用的做法可能会被社区所“推荐”、批准和采用。使您免于处理一些陷阱(例如不需要的重新组合、无限的导航主机调用(您已经处理过这个)等..),但是否遵循它取决于您。
所以你想要理解的就是“状态提升”。我可以通过简单地采样一个场景来解释这一点(这也是基于我自己在 Jetpack Compose 中应用知识的经验)。 考虑具有 3 种不同复杂程度的登录用例
登录 UI 原型:— 只是展示您潜在的登录屏幕设计和用户交互
对于登录原型,我不需要状态类或视图模型,因为它只是一个原型
@Composable
fun LoginScreen() {
val userName by remember { <mutable string state username> }
val password by remember { <mutable string state password> }
Column {
Text(text = username)
Text(text = password)
Button("Login")
}
}
而且因为它是一个非常简单的 UI(可组合),我只需要使用记住 + 状态指定可组合的基本结构,展示正在发生的输入。
对于具有简单验证的登录模型,我们使用了推荐的状态提升,使用类,
class LoginState {
var event;
var mutableUserNameState;
var mutablePasswordState;
fun onUserNameInput() {...}
fun onPasswordInput() {...}
fun onValidate() {
if (not valid) {
event.emit(ShowToast("Not Valid"))
} else {
event.emit(ShowToast("Valid"))
}
}
}
@Composable
fun LoginScreen() {
val loginState by remember { LoginState }
LaunchedEffect() {
event.observe {
it.ShowToast()
}
}
Column {
Text(text = loginState.mutableUserNameState, onInput = { loginState.onUserNameInput()} )
Text(text = loginState.mutablePasswordState, onInput = { loginState.onPasswordInput()} )
Button(loginState.onValidate)
}
}
现在是一个完整的登录模块,您还需要考虑生命周期范围
class LoginViewModel(
val userRepository: UserRepository // injected by your D.I framework
): ViewModel {
var event;
var mutableUserNameState;
var mutablePasswordState;
fun onUserNameInput() {...}
fun onPasswordInput() {...}
fun onValidateViaNetwork() {
// do a non-blocking call to a server
viewModelScope.launch {
var isUserValid = userRepository.validate(username, password)
if (isUserValid) {
event.emit(ShowToast("Valid"))
} else {
event.emit(ShowToast("Not Valid"))
}
}
}
}
@Composable
fun LoginScreen() {
val userNameState by viewModel.mutableUserNameState
val passwordState by viewModel.mutablePasswordState
LaunchedEffect() {
event.observe {
it.ShowToast()
}
}
Column {
Text(text = userNameState, onInput = { viewModel.onUserNameInput()} )
Text(text = passwordState, onInput = { viewModel.onPasswordInput()} )
Button(viewModel.onValidateViaNetwork)
}
}
再次强调,这只是基于我的经验以及我如何决定提升我的状态。至于我包含的片段,我尝试使它们尽可能伪,而不使它们看起来脱离上下文,因此它们不可编译。另外,模拟和原型被认为是相同的,我只是结合使用它们来将事物放入上下文中。
@Composable
fun rememberScrollState(initial: Int = 0): ScrollState {
return rememberSaveable(saver = ScrollState.Saver) {
ScrollState(initial = initial)
}
}
@Stable
class ScrollState(initial: Int) : ScrollableState {
/**
* current scroll position value in pixels
*/
var value: Int by mutableStateOf(initial, structuralEqualityPolicy())
private set
//其余代码 }
这是 Jetpack Compose 中的常见方法。我在我构建的库中使用这种方法,例如在这个
图像裁剪库中,我保持状态和动画。 Animatable 是低级默认动画类,也拥有自己的状态。
@Suppress("NotCloseable")
class Animatable<T, V : AnimationVector>(
initialValue: T,
val typeConverter: TwoWayConverter<T, V>,
private val visibilityThreshold: T? = null
) {
internal val internalState = AnimationState(
typeConverter = typeConverter,
initialValue = initialValue
)
/**
* Current value of the animation.
*/
val value: T
get() = internalState.value
/**
* Velocity vector of the animation (in the form of [AnimationVector].
*/
val velocityVector: V
get() = internalState.velocityVector
/**
* Returns the velocity, converted from [velocityVector].
*/
val velocity: T
get() = typeConverter.convertFromVector(velocityVector)
/**
* Indicates whether the animation is running.
*/
var isRunning: Boolean by mutableStateOf(false)
private set
/**
* The target of the current animation. If the animation finishes un-interrupted, it will
* reach this target value.
*/
var targetValue: T by mutableStateOf(initialValue)
private set
}
等等。这种方法适用于不涉及业务逻辑但涉及 Ui 逻辑的 ui 组件。
当您需要根据业务逻辑(例如搜索或从 API 获取结果)更新 Ui 时,您应该使用 Presenter 类,该类也可以是 ViewModel。
最后但最不重要的一点是,人们现在质疑是否应该有一个带有 Jetpack Compose 的 ViewModel,因为我们可以使用带有 AAC ViewModel 的状态。而且cashapp推出了
分子库状态持有者