androidkotlinandroid-fragmentsfragment-lifecycle

Fragment popbackstack trigger lifecyclescope collect


Situation

I submit data setTripDeliver, the collect works fine (trigger LOADING and then SUCCESS). I pressed a button go to next fragment B (using replace). After that, I press back button (using popbackstack). the collect SUCCESS triggered.

Codes Related

These codes at the FragmentA.kt inside onViewCreated.

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

These codes when to submit data at a button setOnClickListener.

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

Method to collect flow 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()
            }
        }
    }
}

Below codes are from ViewModel.

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

This method to call repository.

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

As you can see, the last Success is called again AFTER I pressed back button (popbackstack)

Question

How to make it trigger once only? Is it the way I implement it is wrong? Thank you in advance.


Solution

  • I have found the solution, thanks to @Nurseyit Tursunkulov for giving me a clue. I have to use SharedFlow.

    At the ViewModel, I replace the initialize with these:

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

    At the replay I have to use 0, so this SharedFlow will trigger once. Next, change _tripDeliver.value to _tripDeliver.emit() like the codes below:

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

    I hope this answer will help the others also.