I’m struggling in binding the UI(SwiftUI especially) to the ViewModel(StateFlow), and wondering if t...
a
I’m struggling in binding the UI(SwiftUI especially) to the ViewModel(StateFlow), and wondering if there is an easy way to bind between them like Compose&StateFlow
j
Would recommend taking a look at https://github.com/rickclephas/KMM-ViewModel
a
Previously, I followed this way but stuck when I wanted to bind a
TextField
with
let
variable in SwiftUI, because TextField needs
@State
or
@Binding
as a value which the mentioned way doesn’t provide, does this library solves the mentioned issue?
a
As @John O'Reilly suggests, the KMM-ViewModel and KMP-NativeCoroutines (https://github.com/rickclephas/KMP-NativeCoroutines) projects from Rickclephas have been immensely useful to us. They allow you to use a MutableStateFlow for modifiable state, and also provide methods to translate one state into another (for example, mapping a mutable state with a list of notifications to a hasNotifications state with stateIn methods). The one gotcha we have run into with the library is that Swift cannot initialize the same state classes more than once, so it often makes sense to have an app level or singleton ViewModel that is used by a ScreenViewModel that provides state with a new instance for each screen. There are also convenient ways to pass along view model objects through the .environmentObject in Swift.
A simple example of a viewmodel with mutable state and how to use it as an ObservedObject in swift.
Copy code
// Kotlin : KMMViewModel Class
class CommunicationScreenViewModel : KMMViewModel(), KoinComponent {

    // Singleton ViewModel providing data we wish to hold for the life of the app
    val communication: CommunicationViewModel = get()

    // Mutable state list of conversations
    @NativeCoroutinesState
    val conversations = MutableStateFlow<List<Conversation>(viewModelScope, emptyList())

    // Mutable state computed from conversation to show the count for example
    @NativeCoroutinesState
    val conversationCount = communication.conversations.map { it.size }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)

}

// Swift : Get an instance (however you prefer, we use Koin) as an Observed model
@ObservedViewModel private var model: CommunicationScreenViewModel = koin.get()
You can then use the view model object properties as is
model.conversations
for example in your swift views. The one quirk we have not overcome is we don't get any code completion for the state properties (they are generated as extension properties by KMP under the hood with @Objcname for the name). Coroutines work beautifully with code completion and Task / await though. I would personally appreciate it if anyone knows how to get code completion for the state properties or at least confirmation that there is no way for it to work yet. I hope this helps!
a
Thank you for the great info, but I see that we can’t scope the VM to the screen in that case, is that correct? if so, how did you overcome this problem?
a
You can and should scope each ViewModel to the screen and even share it via the environment in Swift (which we are currently getting used to now). The limitation is that KMMViewModel only allows Swift to "wrap" each instance once meaning if pass the Kotlin instance of the view model to Swift to two screens that share that same instance it will fail with an error that it cannot be wrapped more than once. Sharing the instance via the Swift environment, using the
environmentObject
and
@EnvironmentViewModel
provided by KMMViewModel seems to work well instead of using koin to get the instance in two separate places so you don't have this issue (you don't make the kotlin <-> swift jump twice with the same instance). We also found that some "application" ViewModels appropriately share the same data with more than one "screen" ViewModel. In those cases the singleton application view model is never directly used outside of shared code. The screen view models used in swift can then share that data using the stateIn methods so you have as many instances of the screen view model as you need and never have the issue there. Primarily, this was just a limitation that we had to learn the workarounds for that we believe has more to do with the way Kotlin / Swift integrate than anything.
I hope my descriptions are clearer and more helpful, but I believe these suggest how we've been able to have view models that correlate to each screen in our apps and (view / data models) that have a longer life for shared data that is often fed to multiple screens.
a
I understood that KMMVM has a limitation that you can't share the same VM instance to two screens, but we can solve this by using
environmentObject
and
@EnvironmentViewModel
. is that correct? And do you have an open source project uses this way?
a
Yes, that is my understanding and how we are seeing it so far. I don't have an open source project using this way ourselves, but I'm happy to share a more complete example as I get a chance.
473 Views