Fragment popbackstack 触发器生命周期范围收集

问题描述

情况

我提交数据 setTripDeliver,收集工作正常(触发 LOADING,然后触发 SUCCESS)。我按下按钮转到下一个片段 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

如您所见,在我按下后退按钮 (Success) 后,最后一个 popbackstack 被再次调用

问题

如何让它只触发一次?是我实现它的方式错误吗?提前致谢。

解决方法

这不是您的实现问题,这是因为 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.

,

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

,

在我看来,我认为你在 lifeScope 中使用协程的方式是不正确的。 FragmentA 的 lifeScope 状态再次处于 Started 后,协程将重新启动:

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

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


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

我找到了解决方案,感谢@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))
        }
    }
}

我希望这个答案也能帮助其他人。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...