我涉足 Android 开发是为了好玩。我有一个非常简单的应用程序,允许您创建和编辑用户。我将这些用户存储在 Room 数据库中。我遇到的问题是,当我单击用户列表中的用户时,它使用 UUID 从数据库中获取用户,但它似乎总是不同步,因为它总是显示我之前的选择,不是我现在的。
例如:
这是我的代码的相关部分:
// Room code
@Entity(
tableName = "Users"
)
data class UserEntity(
@PrimaryKey
val userId: String,
@ColumnInfo
var userName: String,
)
@Dao
interface UserDataAccessObject {
@Query("SELECT * FROM Users")
fun getAll(): Flow<List<UserEntity>>
@Query("SELECT * FROM Users WHERE userId = :userId")
suspend fun get(userId: String): UserEntity
@Insert
suspend fun insert(user: UserEntity)
}
class UserRepository @Inject constructor(private val userDao: UserDataAccessObject) {
val allUsers: Flow<List<UserEntity>> = userDao.getAll()
@WorkerThread
suspend fun get(userId: String): UserEntity {
return userDao.get(userId)
}
@WorkerThread
suspend fun insert(user: UserEntity) {
userDao.insert(user)
}
}
@HiltViewModel
class UserViewModel @Inject constructor(private val repository: UserRepository): ViewModel() {
val allUsers: LiveData<List<UserEntity>> = repository.allUsers.asLiveData()
val user = MutableLiveData<UserEntity>()
fun get(userId: String) = viewModelScope.launch {
user.value = repository.get(userId)
}
fun insert(user: UserEntity) = viewModelScope.launch {
repository.insert(user)
}
}
// Compose code
@AndroidEntryPoint
class MainActivity: ComponentActivity() {
@ExperimentalMaterial3Api
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
val userViewModel = hiltViewModel<userViewModel>()
AppTheme {
NavHost(navController = navController, startDestination = AppNavigation.Home.route) {
... // Omitted code
composable(
AppNavigation.User.route + "/{userId}",
arguments = listOf(navArgument("userId") {
type = NavType.StringType
})
) { backstackEntry ->
val userId = backstackEntry.arguments?.getString("userId")
UserSettingViewModelComponent(
navController = navController,
userViewModel = userViewModel,
userId = userId ?: UUID.randomUUID().toString()
)
}
}
}
}
}
}
@ExperimentalMaterial3Api
@Composable
fun UserSettingViewModelComponent(navController: NavHostController, userViewModel: UserViewModel, userId: String) {
userViewModel.get(userId) // The userId is correct and what is expected
var entity = userViewModel.user.observeAsState().value // This is always the previous selection
var isNew = false;
if (entity == null) {
isNew = true;
entity = UserEntity(userId, "Default User")
}
UserSettingViewModelComponent(navController = navController, userViewModel = userViewModel, userEntity = entity, isNew = isNew)
}
我认为问题在于
userViewModel.get(userId)
异步运行,但我希望可组合项在检索它时使用正确的数据进行更新。
任何帮助将不胜感激。很明显,我不明白它应该如何运作。
observeAsState()
功能。请尝试按如下方式更新您的代码:
var entity by userViewModel.user.observeAsState()
除此之外,你的代码似乎还有一些缺陷。我看到的主要问题是 entity
仍然是
null
,而它仍在从数据库加载。因此,即使 ViewModel 尚未完成加载,您的代码也会立即分配一个
Default User
。因此,您可能应该编写代码来辨别三种不同的状态:
Default User
传递给下一个Composable
此外,在每次重组时,userViewModel.get()
函数将再次执行。这可以通过使用
LaunchedEffect(Unit)
来防止。我还想知道您是否真的需要在您的情况下使用 LiveData 或 Flows。如果您需要使实体及时了解对数据库所做的任何更改,您应该更喜欢使用
Flow。否则,您可以完全摆脱使用 LiveData。只需在 ViewModel 中声明 user
,如下所示:
@HiltViewModel
class UserViewModel @Inject constructor(private val repository: UserRepository): ViewModel() {
val allUsers: LiveData<List<UserEntity>> = repository.allUsers.asLiveData()
var user by mutableStateOf<UserEntity?>(null)
// We can make this a suspend function as we will be calling
// it from a LaunchedEffect block
suspend fun get(userId: String) {
user = repository.get(userId)
}
fun insert(user: UserEntity) = viewModelScope.launch {
repository.insert(user)
}
}
然后在您的可组合项中使用它,如下所示,并且仅 get() 用户一次:
@ExperimentalMaterial3Api
@Composable
fun UserSettingViewModelComponent(navController: NavHostController, userViewModel: UserViewModel, userId: String) {
LaunchedEffect(Unit) { // passing Unit means it will be executed only once
userViewModel.get(userId) // call suspend function here
}
// The userId is correct and what is expected
val entity = userViewModel.user
if (entity == null) {
CircularLoadingIndicator()
} else {
UserSettingViewModelComponent(navController = navController, userViewModel = userViewModel, userEntity = entity, isNew = isNew)
}
}