Closed Elelan closed 2 months ago
In SwiftUI you would need to manually create a Binding
:
TextField("Username", text: Binding {
viewModel.uiState.value.username
} set: { value in
viewModel.onAction(LoginAction.OnTextChanged(LoginInputType.USERNAME, value))
}
)
Using a generated Binding
through the $
only works for mutable properties.
Thank you for your response. I have another question regarding using KMP-ObservableViewModel
in SwiftUI
if you don't mind.
How can we pass events from LoginViewModel
to LoginScreen.swift
?
Here is the LoginEvent
sealed class:
sealed class LoginEvent {
data class LoginSuccess(val userType: UserType) : LoginEvent()
data class LoginFailed(val error: String) : LoginEvent()
data class ShowMessage(val message: String) : LoginEvent()
}
In the LoginViewModel, we send events to the UI as follows:
private val _uiEvent: MutableSharedFlow<LoginEvent> = MutableSharedFlow()
val uiEvent = _uiEvent.asSharedFlow()
private suspend fun handleLoginSuccess(userType: UserType) {
_uiEvent.emit(LoginEvent.LoginSuccess(userType))
}
How can we listen to these events in the LoginScreen.swift
and handle them appropriately? Specifically, how can we observe these uiEvent flows in SwiftUI and react to them?
If we can't use SharedFlow
, is there any alternatives for doing this?
Thank you for your assistance!
There is no built-in support for SharedFlow
s.
Though depending on your goal there are a couple of ways you can consume such a Flow from Swift.
Could you share how you are consuming those events in Android?
In android, I do the following;
@Composable
fun LoginScreen(
viewModel: LoginViewModel = viewModel(),
invokeUiEvent: (UiEvent) -> Unit
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(key1 = Unit) {
viewModel.uiEvent.collectLatest { event ->
when (event) {
is LoginEvent.LoginFailed -> invokeUiEvent(UiEvent.ShowSnackBar(event.error))
is LoginEvent.LoginSuccess -> {
invokeUiEvent(
UiEvent.Navigation.Navigate(
AppScreen.Home.Dashboard.withArgs(ArgParams.USER_TYPE to event.userType),
AppNavController.ROOT
)
)
}
is LoginEvent.ShowMessage -> invokeUiEvent(UiEvent.ShowSnackBar(event.message))
}
}
}
LoginScreenContent(
state = uiState,
invokeUiEvent = invokeUiEvent,
onAction = viewModel::onAction
)
}
Could you suggest a way to consume these event flows in SwiftUI
?
I can use kotlinx.coroutines.channels
for LoginEvent
as well if it's possible to collect it in SwiftUI
.
private val _uiEvent = Channel<LoginEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
Thanks. If you are looking for a similar approach in SwiftUI then you might want to take a look at onReceive(_:perform:)
.
It will perform an action when a publisher emits a value.
For that you would need to convert your SharedFlow
to a Combine Publisher
which can be done with KMP-NativeCoroutines.
I guess it would be easiest to create a Swift subclass of the ViewModel, creating and storing the publisher in the init
.
Hi! I have a question regarding SwiftUI bindings with viewModel.uiState.
In the following
LoginViewModel
, I've created auiState
object to hold theLoginState
. On the Android client, I'm able to use theuiState
values and update them without issues. However, I am having difficulty figuring out how to use thisviewModel
with an iOS SwiftUI client. Specifically, I am unsure how to update theTextField
values in SwiftUI and reflect those updates back into ourviewModel.uiState
.Is there any way to directly bind the
TextField
values to properties withinuiState
in SwiftUI, similar to how we do it in Android? Or is there an alternative approach besides initializing each text value separately in theviewModel
instead of using auiState
object?Here is the code for my
LoginViewModel
:The
LoginState
data class:And the
LoginAction
sealed class:In Android, the
LoginScreen
composable uses theuiState
as follows:How do we implement this in SwiftUI? Here is my attempt, which does not work as expected: