icerockdev / moko-mvvm

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

iOS nullable value from CStateFlow #181

Closed kramlex closed 1 year ago

kramlex commented 2 years ago

mokoMvvmFramework does not allow to get a iOS nullable value from CStateFlow, , it is necessary to add at the moment, force unwrap is being used there

func stateNullable<T, R>(
        _ flowKey: KeyPath<Self, CStateFlow<T>>,
        equals: @escaping (T?, T?) -> Bool,
        mapper: @escaping (T?) -> R?
    ) -> R? {
        let stateFlow: CStateFlow<T> = self[keyPath: flowKey]
        var lastValue: T? = stateFlow.value

        var disposable: DisposableHandle? = nil

        disposable = stateFlow.subscribe(onCollect: { value in
            if !equals(lastValue, value) {
                lastValue = value
                self.objectWillChange.send()
                disposable?.dispose()
            }
        })

        return mapper(stateFlow.value)
    }
Снимок экрана 2022-06-03 в 9 03 36 PM
ln-12 commented 1 year ago

Hey @kramlex, I tried to use your solution, but could not get it working. I added a debug output, but it only prints nil. I double checked on the KMM side, added a output there and got the correct state after the update.

func stateNullable<T, R>(
    _ flowKey: KeyPath<Self, CStateFlow<T>>,
    equals: @escaping (T?, T?) -> Bool,
    mapper: @escaping (T?) -> R?
) -> R? {
    let stateFlow: CStateFlow<T> = self[keyPath: flowKey]
    var lastValue: T? = stateFlow.value

    var disposable: DisposableHandle?

    disposable = stateFlow.subscribe(onCollect: { value in
        print(value)
        if !equals(lastValue, value) {
            lastValue = value
            self.objectWillChange.send()
            disposable?.dispose()
        }
    })

    return mapper(stateFlow.value)
}

My code in the KMM module looks like this:

private var _availableItems = MutableStateFlow<List<Item>?>(null)
val availableItems = _availableItems.cStateFlow()

Inside my SwiftUI view, I am calling it like this:

vehicles: viewModel.stateNullable(\.availableItems, equals: { $0 === $1 }, mapper: { $0 as! [Item]? }),

The normal/predefined states work just fine. Do you have any hint what I am missing?

Edit: after switching to a non-nullable list, I got the similar effect that the list value is not updated and stays empty. So from m experience, there seems to be a problem with lists in general.

Edit 2: I could work around this limitation by using the state pattern in your example. As the state is always non-nullable, the code from above is not even need. Nevertheless, it would still be nice if nullable data types would be supported.

kramlex commented 1 year ago

Hi, @ln-12. I analyzed your problem and couldn't reproduce it. The proposed solution works correctly. If the problem hasn't been solved yet, then I would like to take a look at your view model or case.

ln-12 commented 1 year ago

Thanks @kramlex, I found another approach which is working fine for me.

Alex009 commented 1 year ago

will be released in 0.15.0