Mapping of shared ViewModel UI State in Swift not working
jflavio11 opened this issue · 2 comments
Hello!
I need to map a list of items that come from the shared View Model into objects that implement the Identifiable
protocol (needed for showing them in a List or pass them to Maps and display markers).
This is the exposed state from the ViewModel in Kotlin:
// ui state data class
data class HomeUiState(
val isLoading: Boolean = true,
val closeVenues: List<Venue> = emptyList(),
val errorMessage: String? = null
)
// state defined in ViewModel.kt inside shared module
@NativeCoroutinesState
val uiState = _uiState.asStateFlow().stateIn(
viewModelScope, SharingStarted.WhileSubscribed(), HomeUiState()
)
And, in my iOS app, I'm trying to map it like this:
class HomeViewModel : shared.HomeViewModel {
@Published var venues: [UiVenue] = []
override init() {
super.init()
venues = uiState.closeVenues.compactMap { venue in
venue.toUiVenue() // the UiVenue struct implements Identifiable protocol
}
}
}
...
// ContentView.swift
@ObservedViewModel var viewModel = HomeViewModel()
var body: some View {
List(viewModel.venues) { venue in
Text("Hello \(venue.name)").foregroundColor(Color.black)
}.onAppear {
viewModel.getCloseVenues()
}
}
However, the list is not rendering. I've tried using @State also, but nothing happens. The uiState.closeVenues
returns a simple [Venue]
object, not a Combine Observable.
However, if I use directly viewModel.uiState.closeVenues
from the view, it works fine 🤔
Environment
- Kotlin: 1.8.0
- commonMain KMMViewModel dependency: com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-3
- nativeCoroutines: 1.0.0-ALPHA-4
- ksp: 1.8.0-1.0.9
PS: this library is great, it has saved me a lot of time
Hi, glad to hear you find the library useful!
@NativeCoroutinesState
converts your properties to two separate properties. In your case uiState
and uiStateFlow
.
The first exposes the current value of the StateFlow
and the second exposes a callback wrapper for the Flow
.
You can use the KMP-NativeCoroutines Combine wrappers to create a Combine publisher for the uiStateFlow
.
Something like the following should work:
import KMPNativeCoroutinesCombine
class HomeViewModel : shared.HomeViewModel {
@Published var venues: [UiVenue] = []
override init() {
super.init()
createPublisher(for: uiStateFlow).map {
$0.closeVenues.compactMap { venue in
venue.toUiVenue()
}
}.assertNoFailure().assign(to: &$venues)
}
}
Thank you, @rickclephas
The following was working:
// ContentView.swift
@SwiftUI.State private var venues: [UiVenue] = []
...
View()
.onChange(of: viewModel.uiState) { updated in
venues = updated.closeVenues.map({ venue in
venue.toUiVenue()
})
}
However, your approach is way clean. Thank you so much!