从房间数据库检索的实体与可组合项中使用的实体之间的同步问题

问题描述 投票:0回答:1

我涉足 Android 开发是为了好玩。我有一个非常简单的应用程序,允许您创建和编辑用户。我将这些用户存储在 Room 数据库中。我遇到的问题是,当我单击用户列表中的用户时,它使用 UUID 从数据库中获取用户,但它似乎总是不同步,因为它总是显示我之前的选择,不是我现在的。

例如:

  1. 我打开应用程序,列出了 2 个用户。
  2. 我单击 user1,它向我显示具有默认值的 UserView
  3. 我返回,单击 user2,它向我显示带有 user1 值的 UserView
  4. 我返回,再次单击 user1,它会显示带有 user2 值的 UserView。
  5. 我返回,再次单击 user2,它会显示带有 user1 值的 UserView。

这是我的代码的相关部分:

// 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)
异步运行,但我希望可组合项在检索它时使用正确的数据进行更新。

任何帮助将不胜感激。很明显,我不明白它应该如何运作。

android android-jetpack-compose android-room
1个回答
0
投票

我认为您没有正确使用

observeAsState()
功能。请尝试按如下方式更新您的代码:

var entity by userViewModel.user.observeAsState()
除此之外,你的代码似乎还有一些缺陷。我看到的主要问题是 

entity

 仍然是 
null
,而它仍在从数据库加载。因此,即使 ViewModel 尚未完成加载,您的代码也会立即分配一个 
Default User
。因此,您可能应该编写代码来辨别三种不同的状态:

    用户当前正在加载→显示加载指示器
  • 加载完成,未找到用户→将
  • Default User
    传递给下一个Composable
  • 加载完成,找到用户→将用户传递给下一个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) } }
    
© www.soinside.com 2019 - 2024. All rights reserved.