Wojciech Krystyniak
06/18/2025, 10:28 AMTextFieldState
inside a ViewModel
? I’ve seen it suggested in the docs (e.g., here and here), but I’m not fully convinced — especially because it makes testing harder.
To keep things simple, here’s a test case (imagine that TextFieldState
and snapshotFlow
are part of the ViewModel):
@Test
fun `when text changed, then update the text`() = runTest {
val state = TextFieldState()
snapshotFlow { state.text.toString() }
.test {
assertEquals("", awaitItem())
state.setTextAndPlaceCursorAtEnd("test")
assertEquals("test", awaitItem())
}
}
This test fails with a timeout:
No value produced in 3s
app.cash.turbine.TurbineAssertionError: No value produced in 3s
I found that wrapping the text change with Snapshot.withMutableSnapshot
makes it work:
Snapshot.withMutableSnapshot { state.setTextAndPlaceCursorAtEnd("test") }
…but I’m still not convinced this is a good approach overall. Has anyone dealt with this? Would love to hear your thoughts.Stylianos Gakis
06/18/2025, 11:13 AMstate.text
value, therefore you are not getting a new emission to awaitItem()
on.Wojciech Krystyniak
06/18/2025, 11:46 AMsetTextAndPlaceCursorAtEnd
😅SaurabhS
06/18/2025, 11:53 AMSnapshot.sendApplyNotifications()
instead. Relevant thread:
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1671685168586409Stylianos Gakis
06/18/2025, 12:26 PMbut I'm changing the textI saw the "cursor" part of the function and my brain simply stopped reading after that it seems like, idk wtf I was thinking when I answered this 😂😅setTextAndPlaceCursorAtEnd
Halil Ozercan
06/18/2025, 2:36 PMTextFieldState
more testable. Even if we did not recommend TextFieldState
in ViewModels, people are going to do it. So the testing of it depending on snapshotFlow
and applying change notifications is not ideal. Hopefully we will have more to share soon.Halil Ozercan
06/18/2025, 2:37 PMsnapshotFlow
in this case and why are you testing TextFieldState
's own functionality?Wojciech Krystyniak
06/18/2025, 2:44 PMHalil Ozercan
06/18/2025, 2:52 PMWojciech Krystyniak
06/18/2025, 3:08 PMsnapshotFlow { stateValue }
.onEach { /* fetch something from API */ }
.launchIn(viewModelScope)
In a different function, I programmatically update the text using setTextAndPlaceCursorAtEnd
.
If I didn’t need to set the text manually, I would move the snapshotFlow
into the Composable and just notify the ViewModel when the text changes. But since I need to update the text from the ViewModel too, it gets a bit more complicated.saket
06/18/2025, 6:31 PMTextFieldState
more testable.
this would be superb! we recently introduced a wrapper at cash app for hoisting text inputs in our presenters, but having something official would be better!Halil Ozercan
06/18/2025, 7:09 PMInputTransformation
?Wojciech Krystyniak
06/19/2025, 9:21 AMAlbert Chang
06/19/2025, 10:45 AMEven if we did not recommendWhy is that? Is it simply because the difficulty of testing? I think it's a common pattern because the only way to keep a single source of truth is to putin ViewModelsTextFieldState
TextFieldState
(as part of UI state) in the view model.Halil Ozercan
06/19/2025, 11:35 AMTextFieldState
in ViewModel wasn't the endorsed way, we would still need to make sure that it is not difficult to test.