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.