我正在使用服务定位器(如https://developer.android.com/training/dependency-injection#di-备选方案,但我保证稍后会切换到适当的DI)来处理我的应用程序中的身份验证。我有一个身份验证服务,它具有user
属性,我使用logIn
和logOut
方法设置和取消设置该属性
我希望我的ContentView
能对auth.user
的变化做出反应,但我不太清楚如何。我已经尝试将其包装到by remember { mutableStateOf() }
中,但登录时没有看到任何更新。。知道我缺了什么吗?
提前感谢!(以下片段)
@Composable
fn ContentView() {
val auth = ServiceLocator.auth
var loggedInUser: User? by remember { mutableStateOf(auth.user) } // <-- I would like my composable to react to changes to auth.user
if (loggedInUser) {
ViewA()
} else {
ViewB()
}
}
object ServiceLocator {
val auth = AuthenticationService()
}
class AuthenticationService {
var user: User? = null
fun logIn() {
// sets user...
}
fun logOut() {
// undefs user...
}
在这行的代码片段中
var loggedInUser: User? by remember { mutableStateOf(auth.user) }
您正在创建一个MutableState<User?>
的实例,该实例具有auth.user
当时引用的值的初始值。由于remember { }
,只有当可组合的ContentView
进入组合,然后MutableState
实例在重新组合中被记住并重用时,才会发生这种初始化。
如果以后更改变量auth.user
,则不会发生重新组合,因为存储在loggedInUser
中的值(处于可变状态)没有更改。
mutableStateOf
的文档解释了这个调用在幕后的实际作用
返回一个用传入值初始化的新
MutableState
。
MutableState
类是一个单独的值持有者,Compose会观察其读写操作。此外,对它的写入作为Snapshot
系统的一部分进行事务处理。
让我们仔细分析一下这条信息。
返回一个用传入值初始化的新MutableState。
调用mutableStateOf
返回一个MutableState
实例,该实例使用作为参数传递的值进行初始化。
MutableState类是一个单一值持有者
此类的每个实例都存储一个state值。它可能存储用于实现目的的其他值,但它只公开单个状态值。
其读写由Compose 观察
Compose观察发生在MutableState
实例上的读写操作
这是您遗漏的信息。写入需要发生在MutableState的实例上(在您的情况下为loggedInUser
),而不是发生在作为初始值传入的变量上(在我们的情况下是auth.user
)。
如果你真的想一想,Kotlin中没有内置的机制来观察变量的变化,所以Compose必须有一个包装器才能观察变化是可以理解的。我们必须通过包装器来更改状态,而不是直接更改变量。
知道所有这些,只需将可变状态移动到AuthenticationService
中,事情就会在中工作
import androidx.compose.runtime.mutableStateOf
class AuthenticationService {
var user: User? by mutableStateOf(null)
private set
// ... rest of the service
}
@Composable
fun ContentView() {
val auth = ServiceLocator.auth
// no remember { } block this time because now the MutableState reference is being kept by
// the AuthenticationService so it won't reset on recomposition
val loggedInUser = auth.user
if (loggedInUser != null) {
ViewA()
} else {
ViewB()
}
}
但是,现在您的AuthenticationService
依赖于mutableStateOf
,因此依赖于您可能想要避免的可组合运行时。A";"服务";(或Repository)不需要知道有关UI实现的详细信息。
还有其他选项可以跟踪状态更改,而不依赖于Compose运行时。来自文档部分Compose和其他库
Compose为Android最流行的基于流的应用程序提供了扩展解决方案。这些扩展中的每一个都由不同的工件:
Flow.collectAsState()
不需要额外的依赖项。(因为它是kotlinx-coroutines-core
的一部分)包括在CCD_ 29伪影中的CCD_。
androidx.compose.runtime:runtime-rxjava2:$composeVersion
中包含的Observable.subscribeAsState()
或CCD_ 32伪影。这些工件注册为侦听器,并将值表示为
State
。每当发出新值时,Compose都会重新组合这些零件其中CCD_ 34被使用。
使用KotlinMutableStateFlow
的示例
// No androidx.compose.* dependencies anymore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class AuthenticationService {
private val user = MutableStateFlow<User?>(null)
val userFlow = user.asStateFlow()
fun logIn() {
user.value = User(/* potential parameters */)
}
fun logOut() {
user.value = null
}
}
然后在可组合中,我们收集流作为状态。
import androidx.compose.runtime.collectAsState
@Composable
fun ContentView() {
val auth = ServiceLocator.auth
val loggedInUser = auth.userFlow.collectAsState().value
if (loggedInUser != null) {
ViewA()
} else {
ViewB()
}
}
要了解有关在Compose中使用状态的更多信息,请参阅管理状态的文档部分。这是能够有效地处理Compose中的状态并触发重新计算的基本信息。它还涵盖了状态提升的基本原理。如果你更喜欢编码教程,这里是Jetpack Compose中State的代码实验室。
最终(当你的应用程序变得越来越复杂时),你可能想在你的服务/存储库层和UI层(可组合)之间再加一层。一个层,它将保存和管理UI状态,以便您能够涵盖积极的结果(成功登录)和消极的结果(失败登录)。
如果采用MVVM(Model View ViewModel)方式或MVI(Model View Intent)方式,则ViewModel
s将覆盖该层。在这种情况下,可组合程序本身只管理一些瞬态UI状态,而它们从VM获取(或观察)其余的UI状态,并调用VM执行操作。然后,虚拟机与服务/存储库层交互,并相应地更新UI状态。在谷歌关于使用Jetpack Compose的自动状态观察的视频中,介绍了如何随着复杂性的增加来处理状态。