Home > Enterprise >  Prevent replay of StateFlow after returning from another Activity
Prevent replay of StateFlow after returning from another Activity

Time:03-29

I've an Activity A, with it's ViewModel with StateFlow UI State implementation as said in Android's documentation.

class A_ViewModel: ViewModel() {
    private val _uiState = MutableStateFlow(UIState.None)
    
    val uiState: StateFlow<UIState> = _uiState

    fun onButtonClicked() {
        _uiState.value = UIState.NavigateToB
    }
}
class A_Activity: AppCompatActivity() {

    private val viewModel = // getViewModel()
    
    override fun onCreate() {
        button.setOnClickListener {
            viewModel.onButtonClicked()
        }

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { uiState ->
                    when (uiState) {
                        is UIState.NavigateToB -> {
                            startActivity(Intent(this, B::class.java))
                        }
                        .
                        .
                        else -> { /* do nothing */ }
                    }
                }
            }
        }
    }
}

The problem I'm facing is, when I return back from Activity B to Activity A, since viewModel.uiState.collect { } is called again in onStart() of Activity A, the latest value (UIState.NavigateToB) gets replayed again and Activity B is again launched.

This seems to be a very trivial issue. But I'm unable to think of a safe solution for this.

Sure, there are successful work arounds like setting uiState.value = UIState.None in the onStop() of Activity A. But I'm unable to think of an elegant way of handling this.

CodePudding user response:

for navigation I have a detached field SharedFlow and uiState is used only for UI state, for instance:

in viewmodel

private val _goTo = MutableSharedFlow<NotPrefsRoute>(replay = 0, extraBufferCapacity = 1)
val goTo: SharedFlow<NotPrefsRoute> = _goTo

...

sealed class NotPrefsRoute {
    object Back : NotPrefsRoute()
    object NotificationSettings : NotPrefsRoute()
}

in fragment

launchAndRepeatWithViewLifecycle {
            viewModel.goTo.collect {
                when (it) {
                    Back -> findNavController().popBackStack()
                    NotificationSettings -> activity?.openNotificationSettings()
                }
            }
        }

CodePudding user response:

For one time events you can use Channels instead of flows like this:

 private val _uiState:Channel<UIState> = Channel()
 val uiState = _uiState.receiveAsFlow()

also, you can use MutableSharedFlow if you want more flexibility like to have multiple consumers or keep the last value without recollecting that like this:

 private val _uiState:MutableSharedFlow<UIState> = MutableSharedFlow(extraBufferCapacity=1)
 val uiState:Flow<UIState> = _uiState
  • Related