簡體   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