4

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.

Ticherhaz FreePalestine
  • 2,738
  • 4
  • 20
  • 46

4 Answers4

2

This is not problem of your implementation this is happening because of stateIn() which use used in your viewModel to convert regular flow into stateFlow
If according to your code snippet the success is triggered once again, then why not loading has triggered? as per article, it is showing the latest cached value when you left the screen and came back you got the latest cached value on view.

Resource: 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.

USMAN osman
  • 952
  • 7
  • 13
2

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.

Ticherhaz FreePalestine
  • 2,738
  • 4
  • 20
  • 46
1

I think this is because of coldFlow, you need a HotFlow. Another option is to try to hide and show fragment, instead of replacing. And yet another solution is to keep this code in viewModel.

Nurseyit Tursunkulov
  • 8,012
  • 12
  • 44
  • 78
0

In my opinion, I think your way of using coroutines in lifeScope is incorrect. After the lifeScope status of FragmentA is at Started again, the coroutine will be restarted:

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

So I think: You need to modify this way:


private fun startLifeCycle() {
     viewLifecycleOwner.lifecycleScope.launch {
        launch {
           collectTripDeliver()
        }
        launch {
           collectTripReattempt()
        }
     }
}
Future Deep Gone
  • 831
  • 6
  • 16