sorry for my bad english :(
I want to create app with KMM, and maximize shared code. And only that i want to write twice is UI and Navigation. So i create ViewModel in shared code.
open class ViewModel<T>(initialState: T) where T : ViewState {
private val _state = MutableStateFlow(initialState)
val currentState: T
get() = stateFlow.value
val stateFlow: StateFlow<T> = _state
fun updateState(
context: CoroutineContext = EmptyCoroutineContext,
mapping: (currentState: T) -> T
) { GlobalScope.launch(context) { _state.emit(mapping(_state.value)) } }
}
Where ViewState is just
interface ViewState
that uses to implement in sealed class, that describes current view state. When it changes UI receive change and updates. State can looks like (first free public API that i found returns random dog image):
sealed class DogImageViewState : ViewState {
object Loading : DogImageViewState()
data class Content(val image: String) : DogImageViewState()
}
OKAY
So i create concrete ViewModel to share logic between iOS and android
class DogImageViewModel(initialState: DogImageViewState) : ViewModel<DogImageViewState>(initialState) {
private val interactor : GetDogImageUrlInteractor by dogDi.instance()
init {
getImage()
}
fun reload() {
updateState { DogImageViewState.Loading }
getImage()
}
private fun getImage() {
GlobalScope.launch(ioDispatcher) {
val image = interactor.getImageUrl()
updateState { (it as? Content)?.copy(image = image) ?: Content(image = image) }
}
}
}
Simple, innit? Viewmodel initializes with Loading, and immidiately starts image loading. After image has loaded it goes to new state. On refresh actions exactly the same
In Android usage is very simple, jetpack compose can collectAsState
StateFlow and there nothing to do anymore, just decribe an ui with state
Problem is in iOS
We must "bridge" observable model for iOS. iOS has ObservableObject
mechanism and I has extended it
class ObservableViewModel<T : ViewState>: ObservableObject {
@Published var state : T
init(viewModel: ViewModel<T>) {
self.state = viewModel.currentState
viewModel.onChange { newState in
self.state = newState as! T
}
}
}
Looks good. Generic class for ViewState... At now i can use it with SwiftUI! Oh no...
When I try to use ObservableViewModel
in code i receive an error
@StateObject var vm = ObservableViewModel<DogImageViewState>(viewModel: DogImageViewModel(initialState: DogImageViewState.Loading()))
Type 'DogImageViewState' does not conform to protocol 'ViewState'
But it is exactly conforms!!!
When i jump to definition DogImageViewState
i see next
__attribute__((swift_name("DogImageViewState")))
@interface DogDogImageViewState : DogBase <DogSharedViewState>
@end;
Looks like not conforms at all. What is the Base<ViewState>
(DogBase because Dog feature builds in separate framework dog
and DogSharedViewState because of ViewState declared in shared
)? Why is not just the ViewState
?
How to explain to swift that DogSharedViewState and exactly the same?