繁体   English   中英

返回时刷新viewmodel中的数据,android

[英]refresh data in viewmodel when navigating back, android

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

为了拥有单一的真实来源,我从 nodeJS 后端获取数据,然后将其保存到本地存储库(房间)。 我想我需要刷新 viewModel 中的状态以从我的存储库中获取更新的列表。

我知道我可以使用 Job 来执行此操作,但它会抛出一个错误。 这是返回 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

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

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

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

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
}

然后可以在视图模型中的其他地方完成从结果创建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 fe 中),您都可以使用

val state by viewModel.state.collectAsStateWithLifecycle()

多亏了 kotlin flows, state将始终包含来自房间数据库的最新数据,多亏了 compose 魔法,当state对象中的任何内容更新时,您的可组合项将始终更新,因此您实际上只有一个单一的真实来源。

此外:

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

在@Leviathan 能够为我指出正确的方向之后,我通过更改我的存储库函数的返回类型、实现用例并返回Flow<List<Plant>>而不是Flow<Resource<List<Plant>>> >> 来重构我的代码Flow<Resource<List<Plant>>>为简单起见。

正如 Leviathan 所指出的,进一步删除了PlantDao.ktPlantRepository.kt中函数的挂起标记。

// PlantRepositoryImplementation.kt
override fun getPlants(
  fetchFromBackend: Boolean,
  query: String
): Flow<List<Plant>> {
  return flow {
    val localPlants = dao.searchPlants(query)
    localPlants.collect { plants ->
      emit(plants.map { it.toPlant() })
      val isDbEmpty = plants.isEmpty() && query.isBlank()
      val shouldLoadFromCache = !isDbEmpty && !fetchFromBackend
      if (shouldLoadFromCache) {
        return@collect
      }
      val response = plantApi.getPlants().plants
      dao.clearPlants()
      dao.insertPlants(
        response.map { it.toPlantEntity() }
      )
      emit(response)
    }
  }
}

我开始在我的 viewModel 中使用 Job 和 GetPlants 用例,如下所示:

// PlantsViewModel.kt
private fun getPlants(plantOrder: PlantOrder, fetchFromBackend: Boolean, query: String) {
  getPlantsJob?.cancel()
  getPlantsJob = plantUseCases.getPlants(plantOrder, fetchFromBackend, query)
    .onEach { plants ->
      _state.value = state.value.copy(
        plants = plants,
        plantOrder = plantOrder
      )
    }.launchIn(viewModelScope)

我还必须删除PlantDao.kt中的暂停

// PlantDao.kt
fun searchPlants(query: String): Flow<List<PlantEntity>>

这是我的 GetPlants 用例的代码:

// GetPlantsUsecase.kt
class GetPlants
  (
  private val repository: PlantRepository,
) {
  operator fun invoke(
    plantOrder: PlantOrder = PlantOrder.Name(OrderType.Descending),
    fetchFromBackend: Boolean,
    query: String
  ): Flow<List<Plant>> {
    return repository.getPlants(fetchFromBackend, query).map { plants ->
      when (plantOrder.orderType) {
        is OrderType.Ascending -> {
          // logic for sorting
          }
        }
        is OrderType.Descending -> {
          // logic for sorting
          }
        }
      }
    }
  }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM