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

9wbgstp7  于 2022-12-25  发布在  Android
关注(0)|答案(2)|浏览(258)
    • 我有以下设置。**

我有一个带有项目列表的屏幕(PlantsScreen)。当单击列表中的项目时,我将被导航到另一个屏幕(AddEditPlantScreen)。编辑并保存项目并导航回listScreen后,我想显示更新的项目列表。但列表没有显示更新的列表,而是显示编辑项目之前的列表。
为了获得单一的真实来源,我从node. jsBack-End获取数据,然后将其保存到本地存储库(Room)。我认为我需要刷新ViewModel中的状态,以便从我的存储库中获取更新后的列表。
我知道我可以使用一个Job来完成这件事,但是它抛出了一个错误。当返回一个Flow时,这是正确的方法吗?
一个月一个月一个月一个月一个月

    • 工厂列表视图模型. 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))
  }
}

The full code for reference can be found here:https://gitlab.com/fiehra/plants
∮谢谢你∮

owfi6suc

owfi6suc1#

事实上你有两个真相来源:一个是房间数据库,另一个是视图模型中的_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具有其他值,则只需更新变量;流将自动重新计算状态对象。2它可以像调用这样的东西一样简单:

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中),您都可以使用以下命令获取状态对象

val state by viewModel.state.collectAsStateWithLifecycle()

多亏了kotlin流,state将总是包含房间数据库中最新的数据,多亏了compose魔术,当state对象中的任何东西更新时,你的可组合对象将总是更新,所以你真的只有一个单一的真实来源。
此外:

  • PlantRepository.getPlants()不应该被标记为暂停函数,因为它只是创建一个流,不会阻塞;长时间运行的数据检索将在收集器中完成。
  • 您需要手动导入androidx.compose.runtime.getValueandroidx.compose.runtime.setValue,以便某些代理能够工作。
cqoc49vn

cqoc49vn2#

在@Leviathan能够为我指出正确的方向之后,我通过改变存储库函数的返回类型、实现用例和返回Flow<List<Plant>>而不是Flow<Resource<List<Plant>>>来重构代码,以简化操作。
进一步删除了Leviathan指出的PlantDao.ktPlantRepository.kt中函数的挂起标记。

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

我开始在我的视图模型中使用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
          }
        }
      }
    }
  }

相关问题