How to update StateFlow.stateIn() again in Android?

I am exposing Ui state to a view(like Activity) from ViewModel by using stateIn() now.
this below is my ViewModel code .

MainViewModel.kt

@HiltViewModel
class MainViewModel @Inject constructor (
	private val listUseCase: GetUserListUseCase
): ViewModel() {
	private var _listUiState = listUseCase.invoke().stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = UiState.Loading
    )
    val listUiState: StateFlow<UiState<List>> = _listUiState
}

And I want to add a function which fetches a new list from an api.
So, I made fetchNewList().

class MainViewModel @Inject constructor (
	private val listUseCase: GetUserListUseCase
): ViewModel() {
	private var _listUiState = listUseCase.invoke().stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = UiState.Loading
    )
    val listUiState: StateFlow<UiState<List>> = _listUiState

    fun fetchNewList() = viewModelScope.launch {
		_listUiState = listUseCase.invoke().stateIn(
	        scope = viewModelScope,
	        started = SharingStarted.WhileSubscribed(5000),
	        initialValue = UiState.Loading
	    )
    }
}

But the code above is not working. Nothing happens…
I feel like the reason why it wasn’t updated is that i didn’t access to _listUiState with .value.
And i also want to use stateIn() in fetchNewList() because SharingStarted is very useful and efficient.
SharingStarted gives me the following benefits which are what i care about now.

â—Ź When the user sends your app to the background, updates coming from other layers will stop after five seconds, saving battery.

â—Ź The latest value will still be cached so that when the user comes back to it, the view will have some data immediately.

â—Ź Subscriptions are restarted and new values will come in, refreshing the screen when available.

So, How can i update the StateFlow with new list by using stateIn(SharingStarted.WhileSubscribed(5000))??
is it possible or if not, i hope i get good alternatives that achieve the same effect of WhileSubscribed().

I don’t know if this is your case, but replacing a flow with a new one usually doesn’t make too much sense. Whoever is collecting the flow will still use the old one.

You can try to use something like this:

private val flow = MutableSharedFlow<Unit>()
val listUiState = flow.flatMapLatest { listUseCase.invoke() }.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),
    initialValue = UiState.Loading
)

fun fetchNewList() = flow.tryEmit(Unit)
1 Like

You are right! I felt like that didn’t make sense at all even though I used the code. :slight_smile:
Your workaround worked well! and it’s really what i expected! thank you so much. :smiley:
But why did you use MutableSharedFlow<T>?
Is there any reason?

I’m not sure if I get your question right. MutableSharedFlow here is a flow that is used to trigger the refresh. We can’t force existing flow to restart itself, but using flatMapLatest() we can create a flow that will switch its source flow from one to another. In this case the source is listUseCase.invoke() and it is invoked every time we emit anything to flow.

Alternatively, we could create custom flow using flow {} and it would call listUseCase.invoke() in an infinite loop, each time waiting for some kind of a signal that we would trigger in fetchNewList(). I just think flatMapLatest() is a simpler solution.

1 Like