When using Compose Desktop, I consistently am seei...
# compose-desktop
c
When using Compose Desktop, I consistently am seeing issues with characters being swapped or dropped while typing into an Material3 OutlinedTextField. For example, typing “hello world” might turn into “ehllo world”. Has anyone else seen this?
I don’t see it happening in Compose for Web (HTML). I’m going to test WASM and Android soon also to see if it occurs there. I’m working to create a reproducible simplified example although that’ll take a little more time.
p
Can you show your code. You need a local remember to save that text data. Are you updating the code from a state hosted outside the composable?
c
State is hoisted outside of the Composable, with a “view model” holding the state in a MutableStateFlow. I can share similar code, but it doesn’t reproduce the problem yet. (I started from scratch trying to reproduce, but it seems I’ll be better served taking the code that reproduces and narrowing it down).
p
Yes that is the problem. You need to keep the state of your text with a local remember. And send a copy to the external State when it updates
Work with the copy. If you host the text completely outside this kind of problems show up. There are a couple of threads in this workspace with relevant information. And there is also an article in the official Android documentation that talks about this problem.
m
Quick question, is there anything wrong with the approach he mentioned? What if we want to preserve that state across configuration changes? Is it possible that the same “ehllo world” problem won’t show up with the data stored in an external state? I’m also wondering how both the remember and the external state will be managed.
I’ll probably test this out of curiosity in the morning
c
Mofe:
rememberSaveable
or using a ViewModel are the two ways to persist across Android configuration changes.
From my understanding of Compose, hoisting the state to a ViewModel shouldn’t be problematic as long as you aren’t doing huge recompositions. I’m using a provider approach to scope the recompositions to final child Composables.
This does NOT reproduce the problem yet. But it gives a sense of how things are structured. It will be a few days before I actually have a reproducible example.
Copy code
fun main() {
    val viewModel = ViewModel()
    application {
        Window(onCloseRequest = ::exitApplication, title = "KotlinProject") {
            App(viewModelProvider = { viewModel })
        }
    }
}

class ViewModel {
    val state = MutableStateFlow("")
}

@Composable
fun App(viewModelProvider: () -> ViewModel) {
    val viewModel = viewModelProvider()
    val textState = viewModel.state.collectAsState()
    MaterialTheme {
        MainContent(
            textProvider = {textState.value},
            updateText = { viewModel.state.value = it }
        )
    }
}

@Composable
fun MainContent(
    textProvider: () -> String,
    updateText: (String) -> Unit
) {
    OutlinedTextField(
        value = textProvider(),
        onValueChange = updateText,
        label = { Text("Enter text") },
        modifier = Modifier.fillMaxWidth()
    )
}
m
What View model library are you using? Or did you write one up yourself?
c
There’s no library. ViewModel is just a plain class outside of the Compose scope for this example.
If I add an Android target, then I’ll have to do some expect/actual to hook into Android’s view model machinery.
But for now this is desktop target only to demonstrate the issue that i’ve been seeing only on desktop.
m
Here is a good read for this issue, it explains how its happening and how to avoid it https://medium.com/androiddevelopers/effective-state-management-for-textfield-in-compose-d6e5b070fbe5
😮 1
☝🏻 2
☝️ 2
p
Perhaps your viewmodel is still light and don't introduce any lag, or delay for the flow or State to dispatch the update. But the moment things get bigger, it is most likely that the update to the text gets milliseconds delay due to the coroutine dispatcher being busy or the text validation (as in the article) anything. This little delay is the culprit. So the solution is updating the text field the quickest possible. And for that you need a local state without any delays. Then just send a copy of it to the ViewModel.
The ViewModel can still do validation and circle back to the composable using another state property that don't interfere the text field data. Let's say a mutableStateOfBoolean that indicates is validation is right or not.
Hopefully BasicTextField2 will do this mechanism for us.
m
Also, the article did support usage of a
MutableState
within the viewModel for managing the state, but the issue stems from managing the textField state across a reactive stream such as stateFlow, in which we can't always guarantee the order the data would be received. I've really learned something new today. PS: Ever notice how Compose Multiplatform already handles saving TextField data across configuration changes by default unlike Jetpack Compose? I wonder what the JB team did differently.
p
MutableState is faster and doesn't have the Flow collection overhead but still can cause undesirable behavior while typing if a delay gets in the middle of the update cycle for whatever reason.