icerockdev / moko-mvvm

Model-View-ViewModel architecture components for mobile (android & ios) Kotlin Multiplatform development
https://moko.icerock.dev/
Apache License 2.0
994 stars 95 forks source link

Is it possible to use interfaces of ViewModel in Swift? #244

Open olegivo opened 1 year ago

olegivo commented 1 year ago

Interfaces are useful in case of faking in previews. For example:


interface ItemViewModel {
    val title: CMutableStateFlow<String>
    val isEditing: CStateFlow<Boolean>
    val canSave: CStateFlow<Boolean>
}

class ItemViewModelImpl(
    private val heavyDependency1: HeavyDependency1,
    private val heavyDependency2: HeavyDependency2,
    ...
    private val heavyDependency101: HeavyDependency101,
): ViewModel(), ItemViewModel {
    override val title = MutableStateFlow("").cMutableStateFlow()
    override val isEditing = MutableStateFlow(false).cMutableStateFlow()
    override val canSave = MutableStateFlow(false).cMutableStateFlow()
}

class ItemViewModelFake(
    title: String,
    isEditing: Boolean = false,
    canSave: Boolean = false,
): ViewModel(), ItemViewModel {
    override val title = MutableStateFlow(title).cMutableStateFlow()
    override val isEditing = MutableStateFlow(isEditing).cStateFlow()
    override val canSave = MutableStateFlow(canSave).cStateFlow()
}

@Composable
fun Item(viewModel: ViewModel /*for the sake of simplicity without DI*/) {
    // some UI
}

And use them for previews:

@Preview
@Composable
private fun ItemPreview() {
    MaterialTheme {
        Item(
            ViewModelFake(title = "123")
        )
    }
}

@Preview
@Composable
private fun ItemEditingCannotSavePreview() {
    MaterialTheme {
        Item(
            ViewModelFake(title = "123", isEditing = true)
        )
    }
}

@Preview
@Composable
private fun ItemEditingCanSavePreview() {
    MaterialTheme {
        Item(
            ViewModelFake(title = "123", isEditing = true, canSave = true)
        )
    }
}

But not for SwiftUI. We can't make extensions for protocols (to be ObservableObject).

darronschall commented 8 months ago

You can support this in SwiftUI with generics. Something like this (apologies if this doesn't compile, writhing this code directly in a comment):

struct ItemView<Model>: View where Model: ItemViewModel & ViewModel {
    @ObservedObject var viewModel: Model

    var body: some View {
       Text(viewModel.state(\.title))
    }
}

struct ItemView_Previews: PreviewProvider {
    static var previews: some View {
        ItemView<ItemViewModelFake>(
            viewModel: ItemViewModelFake()
        )
    }
}