gpt4 book ai didi

android - 当我更改 ViewModel var 时,可组合项不会在 Kotlin + Compose 中更新

转载 作者:行者123 更新时间:2023-12-02 15:49:15 25 4
gpt4 key购买 nike

当我更改 ViewModel 变量时,可组合项不会更新 View ,我不确定该怎么做。

这是我的主要 Activity :

class MainActivity : ComponentActivity() {
companion object {
val TAG: String = MainActivity::class.java.simpleName
}

private val auth by lazy {
Firebase.auth
}

var isAuthorised: MutableState<Boolean> = mutableStateOf(FirebaseAuth.getInstance().currentUser != null)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val user = FirebaseAuth.getInstance().currentUser

setContent {
HeroTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
if (user != null) {
Menu(user)
} else {
AuthTools(auth, isAuthorised)
}
}
}
}
}
}

我有一个 View 模型:

class ProfileViewModel: ViewModel() {
val firestore = FirebaseFirestore.getInstance()
var profile: Profile? = null
val user = Firebase.auth.currentUser

init {
fetchProfile()
}

fun fetchProfile() {
GlobalScope.async {
getProfile()
}
}

suspend fun getProfile() {
user?.let {
val docRef = firestore.collection("Profiles")
.document(user.uid)

return suspendCoroutine { continuation ->
docRef.get()
.addOnSuccessListener { document ->
if (document != null) {
this.profile = getProfileFromDoc(document)
}
}
.addOnFailureListener { exception ->
continuation.resumeWithException(exception)
}
}
}
}
}

以及基于用户身份验证的可组合 View :

@Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel = ProfileViewModel()

Column(
modifier = Modifier
.background(color = Color.White)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,

) {

Text("Signed in!");


ProfileVModel.profile?.let {
Text(it.username);
}

Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
TextButton(onClick = {
FirebaseAuth.getInstance().signOut()
context.startActivity(Intent(context, MainActivity::class.java))
}) {
Text(
color = Color.Black,
text = "Sign out?",
modifier = Modifier.padding(all = 8.dp)
)
}
}
}
}

当我的 Firestore 方法返回时,我更新了 profile var,并“期望”它在可组合项中更新,在这里:

    ProfileVModel.profile?.let {
Text(it.username);
}

然而,什么都没有改变?

当我从可组合内部添加 firebase 函数时,我可以这样做:

context.startActivity(Intent(context, MainActivity::class.java))

它会更新 View 。但是,我不太确定如何从 ViewModel 内部执行此操作,因为“上下文”是 Composable 特定的功能?

我已尝试查找实时数据,但每个教程要么太令人困惑,要么与我的代码不同。我来自 SwiftUI MVVM,所以当我更新 ViewModel 中的某些内容时,任何使用该值的 View 都会更新。这里似乎不是这种情况,我们将不胜感激。

谢谢。

最佳答案

第 1 部分:获取 ViewModel正确

在下面标记的行中,您将 View 模型设置为新的 ProfileViewModel在你的 Menu 的每次重组上实例可组合,这意味着您的 View 模型(以及它跟踪的任何状态)将在每次重组时重置。这会阻止您的 View 模型充当 View 状态持有者。

@Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel = ProfileViewModel() // <-- view model resets on every recomposition

// ...
}

您可以通过始终获取您的 ViewModel 来解决此问题来自 ViewModelStore .以这种方式ViewModel将具有正确的所有者(正确的生命周期所有者),因此具有正确的生命周期。Compose 有一个帮助程序来获取 ViewModel s 与 viewModel() 打电话。

这就是您在代码中使用调用的方式

@Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel: ProfileViewModel = viewModel()
// or this way, if you prefer
// val ProfileVModel = viewModel<ProfileViewModel>()

// ...
}

另见 ViewModels in Compose 概述了与 ViewModel 相关的基础知识s 在 Compose 中。

Note: if you are using a DI (dependency injection) library (such as Hilt, Koin...) then you would use the helpers provided by the DI library to obtain ViewModels.

第 2 部分:避免 GlobalScope (除非你确切地知道你为什么需要它)并注意异常

Avoid Global Scope 中所述你应该避免使用 GlobalScope只要有可能。安卓ViewModel它们带有自己的协程范围,可通过 viewModelScope 访问.你还应该 watch out for exceptions .

代码示例

class ProfileViewModel: ViewModel() {
// ...
fun fetchProfile() {
// Use .launch instead of .async if you are not using
// the returned Deferred result anyway
viewModelScope.launch {
// handle exceptions
try {
getProfile()
} catch (error: Throwable) {
// TODO: Log the failed attempt and/or notify the user
}
}
}

// make it private, in most cases you want to expose
// non-suspending functions from VMs that then call other
// suspend factions inside the viewModelScope like fetchProfile does
private suspend fun getProfile() {
// ...
}
// ...
}

Best practices for coroutines in Android 中涵盖了更多协程最佳实践.

第 3 部分:在 Compose 中管理状态

Compose 通过 State<T> 跟踪状态.如果你想管理状态,你可以创建 MutableState<T> 带有 mutableStateOf<T>(value: T) 的实例, 其中value参数是您要用来初始化状态的值。

你可以像这样在你的 View 模型中保持状态

// This VM now depends on androidx.compose.runtime.*
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

class ProfileViewModel: ViewModel() {
var profile: Profile? by mutableStateOf(null)
private set
// ...
}

那么每次你都会改变profile变量,以某种方式使用它(即读取它)的可组合项将进行重组。

但是,如果你不想要你的 View 模型ProfileViewModel依赖于 Compose 运行时,那么还有其他选项可以在不依赖于 Compose 运行时的情况下跟踪状态更改。来自文档部分 Compose and other libraries

Compose comes with extensions for Android's most popular stream-basedsolutions. Each of these extensions is provided by a differentartifact:

  • Flow.collectAsState() doesn't require extra dependencies. (because it is part of kotlinx-coroutines-core)

  • LiveData.observeAsState() included in the androidx.compose.runtime:runtime-livedata:$composeVersion artifact.

  • Observable.subscribeAsState() included in the androidx.compose.runtime:runtime-rxjava2:$composeVersion or> androidx.compose.runtime:runtime-rxjava3:$composeVersion artifact.

These artifacts register as a listener and represent the values as aState. Whenever a new value is emitted, Compose recomposes those partsof the UI where that state.value is used.

这意味着您还可以使用 MutableStateFlow<T>跟踪 ViewModel 内的变化并将其作为 StateFlow<T> 暴露在您的 View 模型之外.

// This VM does not depend on androidx.compose.runtime.* anymore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

class ProfileViewModel : ViewModel() {
private val _profileFlow = MutableStateFlow<Profile?>(null)
val profileFlow = _profileFlow.asStateFlow()

private suspend fun getProfile() {
_profileFlow.value = getProfileFromDoc(document)
}
}

然后使用 StateFlow<T>.collectAsState() 在您的可组合项中获取 State<T>这是 Compose 所需要的。

A general Flow<T> can also be collected as State<T> with Flow<T : R>.collectAsState(initial: R), where the initial value has to be provided.

@Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel: ProfileViewModel = viewModel()
val profile by ProfileVModel.profileFlow.collectAsState()

Column(
// ...
) {
// ...
profile?.let {
Text(it.username);
}
// ...
}
}

要了解有关在 Compose 中使用状态的更多信息,请参阅 Managing State 上的文档部分.这是能够在 Compose 中使用状态并有效触发重组的基本信息。它还涵盖了 state hoisting 的基础知识.如果您更喜欢此处的编码教程,请访问 code lab for State in Jetpack Compose .

有关随着复杂性增加而处理状态的介绍,请参阅来自 Google 的关于 Using Jetpack Compose's automatic state observation 的视频。 .

关于android - 当我更改 ViewModel var 时,可组合项不会在 Kotlin + Compose 中更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73115009/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com