Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent replay of StateFlow after returning from another Activity

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.

like image 447
Suraj Kumar Sau Avatar asked Nov 22 '25 18:11

Suraj Kumar Sau


2 Answers

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
like image 108
Pejman Azad Avatar answered Nov 24 '25 09:11

Pejman Azad


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()
                }
            }
        }
like image 39
Sergei S Avatar answered Nov 24 '25 08:11

Sergei S



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!