icerockdev / moko-mvvm

Model-View-ViewModel architecture components for mobile (android & ios) Kotlin Multiplatform development

Home Page:https://moko.icerock.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is it possible to use interfaces of ViewModel in Swift?

olegivo opened this issue · comments

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).

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()
        )
    }
}