简体   繁体   English

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

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

So I have the following setup.所以我有以下设置。 I have a screen with a list of items (PlantsScreen).我有一个包含项目列表的屏幕 (PlantsScreen)。 when clicking on an item in the list i will be navigated to another screen (AddEditPlantScreen).单击列表中的项目时,我将导航到另一个屏幕 (AddEditPlantScreen)。 after editing and saving the item and navigating back to the listScreen i want to show the updated list of items.编辑并保存项目并导航回 listScreen 后,我想显示更新的项目列表。 But the list is not displaying the updated list but the list before the edit of the item.但是列表显示的不是更新后的列表,而是项目编辑前的列表。

In order to have a single source of truth i am fetching the data from a nodeJS backend and then saving it to the local repository(room).为了拥有单一的真实来源,我从 nodeJS 后端获取数据,然后将其保存到本地存储库(房间)。 I think i need to refresh the state in the viewModel to fetch the updated list from my repository.我想我需要刷新 viewModel 中的状态以从我的存储库中获取更新的列表。

I know i can use a Job to do this but it throws me an error.我知道我可以使用 Job 来执行此操作,但它会抛出一个错误。 is this the correct approach when returning a Flow?这是返回 Flow 时的正确方法吗? If yes how can i achieve this.如果是,我该如何实现。 If not what alternative approach do i have?如果不是,我有什么替代方法?

// 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,
              )
            }
          }
        }
      }
  }
}

here my repository where i fetch the items for the list from这是我的存储库,我从中获取列表的项目

// 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))
  }
}

full code for reference can be found here: https://gitlab.com/fiehra/plants完整的参考代码可以在这里找到: https ://gitlab.com/fiehra/plants

You actually have two sources of truth: One is the room database, the other the _state object in the view model.您实际上有两个真实来源:一个是房间数据库,另一个是视图模型中的_state对象。

To reduce this to a single source of truth you need to move the collection of the flow to the compose function where the data is needed.要将其简化为单一事实来源,您需要将流的集合移动到需要数据的组合函数。 You will do this using the extension function StateFlow.collectAsStateWithLifecycle() from the artifact androidx.lifecycle:lifecycle-runtime-compose .您将使用工件androidx.lifecycle:lifecycle-runtime-compose中的扩展函数StateFlow.collectAsStateWithLifecycle()来执行此操作。 This will automatically subscribe and unsubscribe the flow when your composable enters and leaves the composition.当您的可组合项进入和离开组合时,这将自动订阅和取消订阅流程。

Since you want the business logic to stay in the view model you have to apply it before the flow is collected.由于您希望业务逻辑保留在视图模型中,因此您必须在收集流之前应用它。 The idea is to only transform the flow in the view model:这个想法是只转换视图模型中的流:

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,
            )
            
    // ...
}

If you want other values for fetchFromBackend and query you just need to update the variables;如果你想要fetchFromBackendquery的其他值,你只需要更新变量; the flow will automatically recalculate the state object.流将自动重新计算状态对象。 It can be as simple as just calling something like this:它可以像这样调用一样简单:

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

The logic to create a PlantsState from a result can then be done somewhere else in the view model.然后可以在视图模型中的其他地方完成从结果创建PlantsState的逻辑。 Replace your PlantsViewModel.getPlants() with this and place it at file level outside of the PlantsViewModel class:用这个替换你的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
    }
}

With the PlantsState class replaced by this: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
}

Then, wherever you need the state (in PlantsScreen fe), you can get a state object with然后,无论您在哪里需要状态(在 PlantsScreen fe 中),您都可以使用

val state by viewModel.state.collectAsStateWithLifecycle()

Thanks to kotlin flows state will always contain the most current data from the room database, and thanks to the compose magic your composables will always update when anything in the state object updates, so that you really only have one single source of truth.多亏了 kotlin flows, state将始终包含来自房间数据库的最新数据,多亏了 compose 魔法,当state对象中的任何内容更新时,您的可组合项将始终更新,因此您实际上只有一个单一的真实来源。

Additionally:此外:

  • PlantRepository.getPlants() should not be marked as a suspend function because it just creates a flow and won't block; PlantRepository.getPlants()不应标记为挂起函数,因为它只是创建一个流而不会阻塞; long running data retrieval will be done in the collector.长时间运行的数据检索将在收集器中完成。
  • You will need to manually import androidx.compose.runtime.getValue and the androidx.compose.runtime.setValue for some of the delegates to work.您需要手动导入androidx.compose.runtime.getValueandroidx.compose.runtime.setValue使某些委托工作。

After @Leviathan was able to point me in the right direction i refactored my code by changing the return types of my repository functions, implementing use cases and returning a Flow<List<Plant>> instead of Flow<Resource<List<Plant>>> for simplicity purposes.在@Leviathan 能够为我指出正确的方向之后,我通过更改我的存储库函数的返回类型、实现用例并返回Flow<List<Plant>>而不是Flow<Resource<List<Plant>>> >> 来重构我的代码Flow<Resource<List<Plant>>>为简单起见。

Further removed the suspend marker of the functions in the PlantDao.kt and PlantRepository.kt as pointed out by Leviathan.正如 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)
    }
  }
}

I started using a Job and GetPlants usecase in my viewModel like this:我开始在我的 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)

I also had to remove the suspend in the PlantDao.kt我还必须删除PlantDao.kt中的暂停

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

This is the code for my GetPlants usecase:这是我的 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.

相关问题 通过 navGraphViewModel 导航返回时保留 ViewModel 实例 - Keep ViewModel instance when navigating back with by navGraphViewModel 在 Android 中导航时如何保留 ViewModel? - How to persist ViewModel when navigating in Android? 导航回来时,Android工具栏变得半透明 - Android Toolbar becomes translucent when navigating back 为什么我的 android 应用程序在使用后退按钮导航和从 Internet 加载数据时会跳帧? (导航组件) - Why my android app is skipping frames when navigating with back button and loading data fron internet? (Navigation component) 向后导航时传输数据 - Transfer Data on Navigating Back 向后导航后,Android LiveData会观察到过时的数据 - Android LiveData observes stale data after navigating back 建议在应用程序中导航时保持数据持久 - Advises to keep data persisted when navigating back in application 按下后退按钮时刷新listview-android - refresh listview when back button is pressed - android Android Espresso导航回活动 - Android Espresso navigating back to an activity Android:浏览片段时更改活动标题; 单击返回按钮时带回相同的标题 - Android: Change activity title when navigating fragments; Bring back same titles when back button clicked
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM