代码之家  ›  专栏  ›  技术社区  ›  Ticherhaz FreePalestine אור מזרחי

碎片弹出式返回触发生命周期范围收集

  •  0
  • Ticherhaz FreePalestine אור מזרחי  · 技术社区  · 5 年前

    情况

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

    相关代码

    这些代码位于 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 在我按下后退按钮后再次被调用( popbackback )

    问题

    如何使其只触发一次?我实施它的方式是错误的吗?提前谢谢。

    0 回复  |  直到 5 年前
        1
  •  2
  •   USMAN osman    5 年前

    这不是您实现的问题,这是因为 stateIn() 在您的 viewModel 将常规流量转换为 stateFlow
    如果根据您的代码片段 成功 再次被触发,那么为什么不呢 加载 已触发? 根据文章,它显示 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.

        2
  •  2
  •   Ticherhaz FreePalestine אור מזרחי    5 年前

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

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

        3
  •  1
  •   Nurseyit Tursunkulov    5 年前

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

        4
  •  0
  •   Future Deep Gone    5 年前

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

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

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

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