Home > database >  Kotlin - StateFlow not emitting updates to its collectors
Kotlin - StateFlow not emitting updates to its collectors

Time:06-27

I got a StateFlow of type UserStateModel (data class) in my app.

private val _userStateFlow: MutableStateFlow<UserStateModel?> = MutableStateFlow(UserStateModel())
val userStateFlow: StateFlow<UserStateModel?> = _userStateFlow

here is the UserStateModel

data class UserStateModel(
    val uid: String? = null,
    val username: String? = null,
    val profileImageUrl: String? = null,
    var isLoggedIn: Boolean = false,
    val isPremiumUser: Boolean = false,
    val posts: List<Post>? = listOf()
) 

When I update the StateFlow with a new Username it emits the change to the collectors and the UI updates. But when I change a property inside the posts: List? list it doesnt emit the changes. When I change the size of the list it does, when I change the name property of the Post at index 0 it doesnt. How can I detect changes to the child properties of the Data class?

Right now I use an ugly workaround, I add

val updateErrorWorkaround: Int = 0 

to the UserStateModel data class and increase it by one so the collectors get notified

P.s I'm using MVVM Clean Architecture and Jeptack Compose

EDIT Thats my Post Model:

data class Post(
    val id: Int,
    val name: String, 
    val tags: MutableList<Tag>? = null

)

Here is how I update the MutableList:

val posts = userStateFlow.value?.posts
posts.get(index).tags?.add(myNewTag)
_userStateFlow.value = userStateFlow.value?.copy(posts = posts)

Those changes are not emitted to the collectors

CodePudding user response:

StateFlow emits only if it detects changes to the value, it ignores replacing the value with the same data. To do this, it compares the previous value with the new one. For this reason, we shouldn't modify the data that we already provided to the StateFlow, because it won't be able to detect changes.

For example, we set value to a User(name=John). Then we mutate the same user object by modifying its name to James and we set the value to this "new" user object. StateFlow compares "new" User(name=James) with its stored value, which is now also User(name=James), so it doesn't see any changes.

In your example you created a copy of UserStateModel, but inside you re-use the same objects and you mutate them. In this case you added a new item to tags and this change affected old UserStateModel as well, so StateFlow doesn't detect the change.

To fix the problem, you need to copy all the data that was changed and do not mutate anything in-place. It is safer to make all the data immutable, so val and List - this way you are forced to make copies. I changed tags to val tags: List<Tag> = listOf(), then your code could look like the following:

val posts = userStateFlow.value?.posts!!.toMutableList()
posts[index] = posts[index].copy(tags = posts[index].tags   myNewTag)
userStateFlow.value = userStateFlow.value?.copy(posts = posts)

Here we create a copy of not only UserStateModel. We also copy posts list, the Post that we modify and we also copy the list of tags.

Alternatively, if this behavior of StateFlow is more annoying to you than helpful, you can use SharedFlow which doesn't compare values, but just emits.

  • Related