I am trying to use the following code:
suspend fun <T> SavedStateHandle.getStateFlow(
key: String,
initialValue: T? = get(key)
): MutableStateFlow<T?> = this.let { handle ->
withContext(Dispatchers.Main.immediate) {
val liveData = handle.getLiveData<T?>(key, initialValue).also { liveData ->
if (liveData.value === initialValue) {
liveData.value = initialValue
}
}
val mutableStateFlow = MutableStateFlow(liveData.value)
val observer: Observer<T?> = Observer { value ->
if (value != mutableStateFlow.value) {
mutableStateFlow.value = value
}
}
liveData.observeForever(observer)
mutableStateFlow.also { flow ->
flow.onCompletion {
withContext(Dispatchers.Main.immediate) {
liveData.removeObserver(observer)
}
}.onEach { value ->
withContext(Dispatchers.Main.immediate) {
if (liveData.value != value) {
liveData.value = value
}
}
}.collect()
}
}
}
I am trying to use it like so:
// in a Jetpack ViewModel
var currentUserId: MutableStateFlow<String?>
private set
init {
runBlocking(viewModelScope.coroutineContext) {
currentUserId = state.getStateFlow("currentUserId", sessionManager.chatUserFlow.value?.uid)
// <--- this line is never reached
}
}
UI thread freezes. I have a feeling it's because of collect()
as I'm trying to create an internal subscription managed by the enclosing coroutine context, but I also need to get this StateFlow as a field. There's also the cross-writing of values (if either changes, update the other if it's a new value).
Overall, the issue seems to like on that collect()
is suspending, as I never actually reach the line after getStateFlow()
.
Does anyone know a good way to create an "inner subscription" to a Flow, without ending up freezing the surrounding thread? The runBlocking {
is needed so that I can synchronously assign the value to the field in the ViewModel constructor. (Is this even possible within the confines of 'structured concurrency'?)
I am in a similar position, but I do not want to modify the value through the LiveData (as in the accepted solution). I want to use only flow and leave LiveData as an implementation detail of the state handle.
I also did not want to have a
var
and initialize it in the init block. I changed your code to satisfy both of these constraints and it does not block the UI thread. This would be the syntax:I provide a scope and use it to launch a coroutine that handles flow's onCompletion and collection. Here is the full code: