Her*_*. F 7 android kotlin firebase android-jetpack-compose
当我更改 ViewModel 变量时,Composable 不会更新视图,我不知道该怎么做。
这是我的主要活动:
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)
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我有一个视图模型:
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)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
以及用户身份验证时的可组合视图:
@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)
)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
当我的 Firestore 方法返回时,我更新profile
var,并“期望”它在可组合项中更新,如下所示:
ProfileVModel.profile?.let {
Text(it.username);
}
Run Code Online (Sandbox Code Playgroud)
然而,什么都没有改变吗?
当我从可组合内部添加 firebase 函数时,我可以这样做:
context.startActivity(Intent(context, MainActivity::class.java))
Run Code Online (Sandbox Code Playgroud)
它会更新视图。但是,我不太确定如何从 ViewModel 内部执行此操作,因为“上下文”是可组合特定的功能?
我尝试查找实时数据,但每个教程要么太混乱,要么与我的代码不同。我来自 SwiftUI MVVM,因此当我更新 ViewModel 中的某些内容时,任何使用该值的视图都会更新。这里的情况似乎并非如此,感谢任何帮助。
谢谢。
ViewModel
correctlyOn the marked line below you are setting your view model to a new ProfileViewModel
instance on every recomposition of your Menu
composable, which means your view model (and any state tracked by it) will reset on every recomposition. That prevents your view model to act as a view state holder.
@Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel = ProfileViewModel() // <-- view model resets on every recomposition
// ...
}
Run Code Online (Sandbox Code Playgroud)
You can fix this by always obtaining your ViewModel
s from the ViewModelStore
. In that way the ViewModel
will have the correct owner (correct lifecycle owner) and thus the correct lifecycle.
Compose has a helper for obtaining ViewModel
s with the viewModel()
call.
This is how you would use the call in your code
@Composable
fun Menu(user: FirebaseUser) {
val context = LocalContext.current
val ProfileVModel: ProfileViewModel = viewModel()
// or this way, if you prefer
// val ProfileVModel = viewModel<ProfileViewModel>()
// ...
}
Run Code Online (Sandbox Code Playgroud)
See also ViewModels in Compose
that outlines the fundamentals related to ViewModel
s in 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
ViewModel
s.
GlobalScope
(unless you know exactly why you need it) and watch out for exceptionsAs described in Avoid Global Scope you should avoid using GlobalScope
whenever possible. Android ViewModel
s come with their own coroutine scope accessible through viewModelScope
. You should also watch out for exceptions.
Example for your code
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() {
// ...
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
More coroutine best practices are covered in Best practices for coroutines in Android.
Compose tracks state through State<T>
. If you want to manage state you can create MutableState<T>
instances with mutableStateOf<T>(value: T)
, where the value
parameter is the value you want to initialize the state with.
You could keep the state in your view model like this
// 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
// ...
}
Run Code Online (Sandbox Code Playgroud)
then every time you would change the profile
variable, composables that use it in some way (i.e. read it) would recompose.
However, if you don't want your view model ProfileViewModel
to depend on the Compose runtime then there are other options to track state changes while not depending on the Compose runtime. From the documentation section Compose and other libraries
Compose comes with extensions for Android's most popular stream-based solutions. Each of these extensions is provided by a different artifact:
Flow.collectAsState()
doesn't require extra dependencies. (because it is part ofkotlinx-coroutines-core
)
LiveData.observeAsState()
included in theandroidx.compose.runtime:runtime-livedata:$composeVersion
artifact.
Observable.subscribeAsState()
included in theandroidx.compose.runtime:runtime-rxjava2:$composeVersion
or >androidx.compose.runtime:runtime-rxjava3:$composeVersion
artifact.These artifacts register as a listener and represent the values as a
State
. Whenever a new value is emitted, Compose recomposes those parts of the UI where thatstate.value
is used.
This means that you could also use a MutableStateFlow<T>
to track changes inside the ViewModel
and expose it outside your view model as a StateFlow<T>
.
// 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)
}
}
Run Code Online (Sandbox Code Playgroud)
And then use StateFlow<T>.collectAsState()
inside your composable to get the State<T>
that is needed by Compose.
A general
Flow<T>
can also be collected asState<T>
withFlow<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);
}
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
To learn more about working with state in Compose see the documentation section on Managing State. This is fundamental information to be able to work with state in Compose and trigger recompositions efficiently. It also covers the fundamentals of state hoisting. If you prefer a coding tutorial here is the code lab for State in Jetpack Compose.
An introduction to handling the state as the complexity increases is in the video from Google about Using Jetpack Compose's automatic state observation.
小智 2
视图模型中的配置文件应为 State<*>
private val _viewState: MutableState<Profile?> = mutableStateOf(null)
val viewState: State<Profile?> = _viewState
Run Code Online (Sandbox Code Playgroud)
可组合的
ProfileVModel.profile.value?.let {
Text(it.username);
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
5302 次 |
最近记录: |