android - 返回时刷新 ViewModel 中的数据 - Android(Kotlin)

标签 android kotlin android-jetpack-compose

我有以下设置。

我有一个带有项目列表的屏幕(PlantsScreen)。单击列表中的某个项目时,我将导航到另一个屏幕(AddEditPlantScreen)。编辑并保存项目并导航回列表屏幕后,我想显示更新的项目列表。但列表中显示的不是更新后的列表,而是项目编辑前的列表。

为了拥有单一的事实来源,我从 node.js 后端获取数据,然后将其保存到本地存储库 (Room)。我认为我需要刷新 ViewModel 中的状态才能从我的存储库中获取更新的列表。

我知道我可以使用作业来执行此操作,但它会引发错误。返回 Flow 时这是正确的方法吗?

如果是,我该如何实现这一点。 如果没有,我有什么替代方法?

plantsListViewModel.kt

private val _state = mutableStateOf<PlantsState>(PlantsState())
val state: State<PlantsState> = _state

init {
  getPlants(true, "")
}
private fun getPlants(fetchFromBackend: Boolean, query: String) {
  viewModelScope.launch {
    plantRepository.getPlants(fetchFromBackend, query)
      .collect { result ->
        when (result) {
          is Resource.Success -> {
            result.data?.let { plants ->
              _state.value = state.value.copy(
                plants = plants,
              )
            }
          }
        }
      }
  }
}

这是我的存储库,我从中获取列表中的项目。

// plantsRepository.kt
override suspend fun getPlants(
  fetchFromBackend: Boolean,
  query: String
): Flow<Resource<List<Plant>>> {
  return flow {
    emit(Resource.Loading(true))
    val localPlants = dao.searchPlants(query)
    emit(
      Resource.Success(
        data = localPlants.map { it.toPlant() },
      )
    )
    val isDbEmpty = localPlants.isEmpty() && query.isBlank()
    val shouldLoadFromCache = !isDbEmpty && !fetchFromBackend
    if (shouldLoadFromCache) {
      emit(Resource.Loading(false))
      return@flow
    }
    val response = plantApi.getPlants().plants
    dao.clearPlants()
    dao.insertPlants(
      response.map { it.toPlantEntity() }
    )
    emit(Resource.Success(
      data = dao.searchPlants("").map { it.toPlant() }
    ))
    emit(Resource.Loading(false))
  }
}

完整的引用代码可以在这里找到: https://gitlab.com/fiehra/plants

谢谢!

最佳答案

您实际上有两个事实来源:一个是房间数据库,另一个是 View 模型中的 _state 对象。

为了将其减少为单一事实来源,您需要将流的集合移动到需要数据的组合函数。您将使用 Artifact androidx.lifecycle:lifecycle-runtime-compose 中的扩展函数 StateFlow.collectAsStateWithLifecycle() 来执行此操作。当您的可组合项进入和离开组合时,这将自动订阅和取消订阅流。

由于您希望业务逻辑保留在 View 模型中,因此您必须在收集流之前应用它。这个想法是只转换 View 模型中的流程:

class PlantsViewModel {
    private var fetchFromBackend: Boolean by mutableStateOf(true)
    private var query: String by mutableStateOf("")

    @OptIn(ExperimentalCoroutinesApi::class)
    val state: StateFlow<PlantsState> =
        snapshotFlow { fetchFromBackend to query }
            .flatMapLatest { plantRepository.getPlants(it.first, it.second) }
            .mapLatest(PlantsState::of)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = PlantsState.Loading,
            )
            
    // ...
}

如果您想要 fetchFromBackendquery 的其他值,您只需更新变量即可;流程将自动重新计算状态对象。它可以像调用这样简单:

fun requestPlant(fetchFromBackend: Boolean, query: String) {
    this.fetchFromBackend = fetchFromBackend
    this.query = query
}

然后可以在 View 模型中的其他位置完成从结果创建 PlantsState 的逻辑。将此替换 PlantsViewModel.getPlants() 并将其放置在 PlantsViewModel 类之外的文件级别:

private fun PlantsState.Companion.of(result: Resource<List<Plant>>): PlantsState = when (result) {
    is Resource.Success -> {
        result.data?.let { plants ->
            PlantsState.Success(
                plants = plants,
            )
        } ?: TODO("handle case where result.data is null")
    }
    is Resource.Error -> {
        PlantsState.Error("an error occurred")
    }
    is Resource.Loading -> {
        PlantsState.Loading
    }
}

PlantsState 类替换为:

sealed interface PlantsState {
    object Loading : PlantsState

    data class Success(
        val plants: List<Plant> = emptyList(),
        val plantOrder: PlantOrder = PlantOrder.Name(OrderType.Descending),
        val isOrderSectionVisible: Boolean = false,
    ) : PlantsState

    data class Error(
        val error: String,
    ) : PlantsState

    companion object
}

然后,无论您在哪里需要状态(在 PlantsScreen f.e. 中),您都可以使用以下命令获取状态对象

val state by viewModel.state.collectAsStateWithLifecycle()

感谢 kotlin flow state 将始终包含房间数据库中的最新数据,并且感谢 compose 的魔力,当 state 中有任何内容时,您的可组合项将始终更新对象更新,这样您实际上只有一个事实来源。

另外:

  • PlantRepository.getPlants() 不应标记为挂起函数,因为它只是创建一个流而不会阻塞;长时间运行的数据检索将在收集器中完成。
  • 您需要手动导入 androidx.compose.runtime.getValueandroidx.compose.runtime.setValue 才能使某些委托(delegate)正常工作。

关于android - 返回时刷新 ViewModel 中的数据 - Android(Kotlin),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74904530/

相关文章:

android - 我需要遍历一组图像并从 native react 中一个接一个地显示它们

Android检测 fragment 何时分离

android - 即使 java gradle 工具链设置为 jdk 11,Gradle 也使用 Java 1.8 构建 android 项目

android - 在 TextView 上设置文本时,在屏幕上显示文本的时间太长

android - 如何在 Jetpack Compose 中实现这种布局

android - 导航 Jetpack Compose 时应用程序崩溃

java - AppCompatSpinner 的条目在第二次选择时不会打开 TimePickerDialog。请查看详情

android - 无法在Android应用程序中显示菜单图标

json - Spring Boot 在 json 中包含 ID 字段

android - Compose SwipeToDismiss 确认状态更改仅适用 >= 阈值