Fragment popbackstack trigger lifecyclescope collect

Fragment popbackstack trigger lifecyclescope collect

情况

我提交数据 setTripDeliver,收集工作正常(触发加载然后成功)。我按下一个按钮转到下一个片段 B(使用 replace)。之后,我按下后退按钮(使用 popbackstack)。触发收集成功。

代码相关

这些代码在FragmentA.kt里面onViewCreated.

private fun startLifeCycle() {
    viewLifecycleOwner.lifecycleScope.launch {
        viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            launch {
                collectTripDeliver()
            }
            launch {
                collectTripReattempt()
            }
        }
    }
}

这些代码何时通过按钮提交数据setOnClickListener

viewLifecycleOwner.lifecycleScope.launchWhenStarted {
    viewModel.setTripDeliver(
         verificationCode,
         remark
    )
}

收集流量的方法collectTripReattempt()

private suspend fun collectTripReattempt() {
        viewModel.tripReattempt.collect {
            when (it) {
                is Resource.Initialize -> {

            }
            is Resource.Loading -> {
                Log.i("???","collectTripReattempt loading")
                handleSaveEarly()
            }
            is Resource.Success -> {
                val error = it.data?.error
                if (error == null) {
                    Tools.showToast(requireContext(), "Success Reattempt")
                    Log.i("???","collectTripReattempt Success")
             
                } else {
                    Tools.showToast(requireContext(), "$error")
                }
                handleSaveEnding()
            }
            is Resource.Error -> {
                handleSaveEnding()
            }
        }
    }
}

以下代码来自ViewModel.

private val _tripDeliver =
    MutableStateFlow<Resource<TripDeliverResponse>>(Resource.Initialize())
val tripDeliver: StateFlow<Resource<TripDeliverResponse>> = _tripDeliver

此方法调用存储库。

suspend fun setTripDeliver(
    verificationCode: String?,
    remark: String?
) {
    _tripDeliver.value = Resource.Loading()
    try {
        val result = withContext(ioDispatcher) {
            val tripDeliverParameter = DeliverParameter(
                verificationCode,
                remark
            )
            val response = appRepository.setTripDeliver(tripDeliverParameter)
            Resource.getResponse { response }
        }
        _tripDeliver.value = result
    } catch (e: Exception) {
        when (e) {
            is IOException -> _tripDeliver.value =
                Resource.Error(messageInt = R.string.no_internet_connection)
            else -> _tripDeliver.value =
                Resource.Error("Trip Deliver Error: " + e.message)
        }
    }
}

Logcat

2021-07-09 19:56:10.946 7446-7446/com.package.app I/???: collectTripReattempt loading
2021-07-09 19:56:11.172 7446-7446/com.package.app I/???: collectTripReattempt Success
2021-07-09 19:56:17.703 7446-7446/com.package.app I/???: collectTripReattempt Success

如您所见,在我按下后退按钮 (popbackstack)

后,最后一个 Success 被再次调用

问题

如何让它只触发一次?是我实现的方式不对吗?提前谢谢你。

这不是您实施的问题,这是因为 stateIn() 在您的 viewModel 中用于将常规流量转换为 stateFlow
如果根据您的代码片段 success 再次触发,那么为什么 loading 没有触发? 根据文章,它显示 latest cached value 当您离开屏幕并返回时,您会看到最新的缓存值。

资源: https://medium.com/androiddevelopers/migrating-from-livedata-to-kotlins-flow-379292f419fb
The latest value will still be cached so that when the user comes back to it, the view will have some data immediately.

在我看来,我认为您在 lifeScope 中使用协程的方式是不正确的。 FragmentA的lifeScope状态再次为Started后,会重启协程:

launch {
   collectTripDeliver()
}
launch {
   collectTripReattempt()
}

所以我认为:你需要这样修改:


private fun startLifeCycle() {
     viewLifecycleOwner.lifecycleScope.launch {
        launch {
           collectTripDeliver()
        }
        launch {
           collectTripReattempt()
        }
     }
}

我认为这是因为coldFlow,你需要一个HotFlow。另一种选择是尝试隐藏和显示片段,而不是替换。另一种解决方案是将此代码保留在 viewModel 中。

我找到了解决方案,感谢@Nurseyit Tursunkulov 给我提供了线索。我必须使用 SharedFlow.

ViewModel,我将初始化替换为这些:

private val _tripDeliver = MutableSharedFlow<Resource<TripDeliverResponse>>(replay = 0)
val tripDeliver: SharedFlow<Resource<TripDeliverResponse>> = _tripDeliver

replay我必须使用0,所以这个SharedFlow会触发一次。接下来,将 _tripDeliver.value 更改为 _tripDeliver.emit(),如下面的代码:

fun setTripDeliver(
    verificationCode: String?,
    remark: String?
) = viewModelScope.launch {
    _tripDeliver.emit(Resource.Loading())

    if (verificationCode == null && remark == null) {
        _tripDeliver.emit(Resource.Error("Remark cannot be empty if verification is empty"))
        return@launch
    }

    try {
        val result = withContext(ioDispatcher) {
            val tripDeliverParameter = DeliverParameter(
                verificationCode,
                remark,
            )
            val response = appRepository.setTripDeliver(tripDeliverParameter)
            Resource.getResponse { response }
        }

        _tripDeliver.emit(result)
    } catch (e: Exception) {
        when (e) {
            is IOException -> _tripDeliver.emit(Resource.Error(messageInt = R.string.no_internet_connection))
            else -> _tripDeliver.emit(Resource.Error("Trip Deliver Error: " + e.message))
        }
    }
}

希望这个回答对其他人也有帮助。