Hey! :wave: I’m using MviKotlin on an Android app...
# mvikotlin
n
Hey! 👋 I’m using MviKotlin on an Android app whose UI is written in Compose and I’m experiencing some weir behaviours with the TextFields. The state is hoisted from the view directly to the MviKotlin Store and the view is observing the state using:
Copy code
val state by store.states.collectAsState()
Everything is good if I tap the keyboard slowly, but when tapping faster the caret starts behaving oddly, not always being positioned after the last character but staying behind. I’ve been reading about this and the recommendation from Google is to return control of the TextField immediately (not do it in an async way). More info here and here. I can’t see a way to do this with MviKotlin, though. Any idea?
1
a
Sure this is a known issue! I believe it's described in the article - https://medium.com/androiddevelopers/effective-state-management-for-textfield-in-compose-d6e5b070fbe5 However the article looks a bit misleading to me, as it states "Avoid holding TextField state using a reactive stream". I dropped a comment there, and provided a fixed implementation - https://gist.github.com/arkivanov/b94b48fad8545012ebec9f9fe163621b TLDR: Use something like
collectAsState(Dispatchers.Main.immediate)
n
Wow, OK! Thank you very much 🙇 .
Can we say this is a bug on the Compose BaseTextField component?
a
I believe so,
collectAsState
could use
Dispatchers.Main.immediate
by default, instead of just
Dispatchers.Main
. This would be aligned with
ViewModel
, which uses
Dispatchers.Main.immediate
internally for its
viewModelScope
.
n
That’s weird, collecting the flow on the
Dispatchers.Main.immediate
dispatcher only did the problem worse 🤔 . Before, it was happening from time to time and now it’s happening always.
To be super sure, your
gist
is using Jetpack’s ViewModel and I’m using a simple MviKotlin store.
a
Make sure you update the state synchronously. When the text changed event is triggered by Compose, the state must be updated and reach Compose on the same call stack. Make sure you are not switching Dispatchers in the
Executor
and call
dispatch(Effect)
directly.
n
Yes, I’m doing that. When collecting the store state I’m using
stateIn
for it to be a
StateFlow
and the coroutine scope passed to
stateIn
is the one provided by the lifecycle of the NavBackStackEntry. Could that be related?
The lifecycle scope is bound to
Main.immediate
too, though.
a
Might be, I'm not sure what is the scope you are using. You can try replacing it with custom scope, just to check. Also, in the next release of MVIKotlin, there will be
stateFlow
extension function, so you won't need
stateIn
. Currently, you can grab the implementation from there: https://github.com/arkivanov/MVIKotlin/blob/d36b55ef4980dcb579956b42baf449d905e06f[…]otlin/com/arkivanov/mvikotlin/extensions/coroutines/StoreExt.kt
n
Yeah, I saw the
stateFlow
extension and I’m eager to use it! I’ll try with a custom scope and with the grabbed implementation and let yo know.
The new stateFlow extension did the trick, so it was related to the scope indeed.
a
Awesome! Thanks for the update.