https://kotlinlang.org logo
#ballast
Title
# ballast
a

Adrian Witaszak

02/13/2023, 10:31 AM
Hi. I've got an issue with my text field adding extra characters. Originally posted here but moved it to GH. https://github.com/copper-leaf/ballast/issues/40
c

Casey Brooks

02/13/2023, 2:46 PM
I’ve seen similar issues to the problem, more generally, of managing TextField state in a StateFlow vs mutableStateOf. The typical solution is to use
Dispatchers.Main.Immediate
as the Dispatcher. Ballast currently uses
Dispatchers.Default
as the default for everything Try adding this into the viewModel configuration, and see if it helps:
Copy code
.dispatchers(
    inputsDispatcher = Dispatchers.Main.immediate,
    eventsDispatcher = Dispatchers.Main.immediate,
    sideJobsDispatcher = <http://Dispatchers.IO|Dispatchers.IO>,
    interceptorDispatcher = Dispatchers.Default
)
a

Adrian Witaszak

02/13/2023, 2:54 PM
Solved. Works as it should now 😄 Thanks
c

Casey Brooks

02/13/2023, 2:56 PM
Good deal, glad it works! The thing to watch out for now is to make sure you’re not making API requests directly in the InputHandler without
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
a

Adrian Witaszak

02/13/2023, 4:17 PM
There is no
<http://Dispatchers.IO|Dispatchers.IO>
in kotlin native, got Main, Default and Unconfined. Currently, for example signIn request i handle in the input, but it is not suspended fun. Scope is created in our sdk. But if it happens i need to make a request and start the scope in Ballast's InputHandler then i should use
withContext(Dispatchers.Default)
? or sideJob maybe?
c

Casey Brooks

02/13/2023, 4:29 PM
Dispatchers.Default
should be a fine replacement, it’s just about keeping long-running tasks off the main thread. Android will throw an exception if you try to make a network/DB call on the main thread, but for other platforms it may not be as strictly enforced but is still a good idea. The decision of whether to wrap a block in
withContext() { }
or move the call to a
sideJob() { }
is basically down to whether you intend to suspend the Ballast VM’s queue while the call is being made. The sideJob is more boilerplate, but frees up the queue so other Inputs can be processed while the API call is still in progress. Making the API call directly in the InputHandler (wrapped in
withContext
) will change based on the InputStrategy. With
FifoInputStrategy
, other Inputs that get sent while the API call is in progress will be queued and won’t be processed until the API completes, which may make the UI unresponsive.
LifoInputStrategy
will cancel the signin request to handle new Inputs immediately. Playing with the KitchenSink example when looking at the Intellij plugin can help you get a feel for what exactly this looks like
a

Adrian Witaszak

02/14/2023, 3:32 PM
i just noticed that the issue still persists on iOS. top textfield is mutableStateOf bottom one using ballast
my VM:
Copy code
internal class LoginViewModel(
    viewModelCoroutineScope: CoroutineScope,
    displayErrorMessage: suspend (String) -> Unit,
    onAuthSuccess: () -> Unit,
) : BasicViewModel<
    LoginContract.Inputs,
    LoginContract.Events,
    LoginContract.State>(
    config = BallastViewModelConfiguration.Builder()
        .apply {
            this += LoggingInterceptor()
            logger = { PrintlnLogger() }
            this += BallastSavedStateInterceptor(
                LoginSavedStateAdapter()
            )
        }
        .withViewModel(
            initialState = LoginContract.State(),
            inputHandler = LoginInputHandler(),
            name = LoginViewModel::class.simpleName,
        )
        .dispatchers(
            inputsDispatcher = Dispatchers.Main.immediate,
            eventsDispatcher = Dispatchers.Main.immediate,
            sideJobsDispatcher = Dispatchers.Default,
            interceptorDispatcher = Dispatchers.Default
        )
        .build(),
    eventHandler = LoginEventHandler(
        displayErrorMessage = displayErrorMessage,
        onAuthSuccess = onAuthSuccess,
    ),
    coroutineScope = viewModelCoroutineScope,
)
c

Casey Brooks

02/14/2023, 4:15 PM
I’m not quite sure the best way to handle that, I haven’t really used Compose on iOS to. You can try collecting the VM state with
.collectAsState(Dispatchers.Main.immediate)
. Or else populate the text field with a
mutableStateOf
and mirror that value in the VM
u

ubuntudroid

10/30/2023, 7:28 PM
Or, if using Compose on both platforms, using
TextFieldValue
instead of
String
in the state might just fix things for you as TextFieldValue also stores cursor position and composition state. With which solution did you settle @Adrian Witaszak?
a

Adrian Witaszak

10/31/2023, 12:17 AM
I ended up using mustableStateOf but will give the TextFieldValue a try
👍 1
17 Views