icerockdev / moko-kswift

Swift-friendly api generator for Kotlin/Native frameworks
https://moko.icerock.dev
Apache License 2.0
348 stars 21 forks source link

UIStateKs not synchronized with UIState class #97

Open aleksey-ho opened 2 weeks ago

aleksey-ho commented 2 weeks ago

I have this code that works in debug environment, but crashes in testflight.

    private val _purchaseResult = MutableStateFlow<UIState<UserPremium>>(UIState.Empty)
    val purchaseResult = _purchaseResult.cStateFlow()

in BasePurchaiseScreen.swift:

struct BasePurchaiseScreen<Content> : View where Content : View {
  @StateObject var vm = PurchaseVM()
...
extension PurchaseVM {
  var purchaseResultBind: UIStateKs<UserPremium> {
    get {
      return self.state(
        \.purchaseResult,
         equals: { $0 === $1 },
         mapper: { UIStateKs($0) }
      )
    }
  }
}

It did work, but somehow it started crashing when I call vm.purchaseResultBind with error:

Crashed: com.apple.main-thread EXC_BREAKPOINT 0x00000001a0ab58c0 0 libswiftCore.dylib 0x398c0 assertionFailure(:_:file:line:flags:) + 264 1 myapp 0x7c850 MPLPurchaseVM.purchaseResultBind.getter + 80 (BasePurchaiseScreen.swift:80) 2 myapp 0x7bb48 closure #1 in BasePurchaiseScreen.body.getter + 42 (BasePurchaiseScreen.swift:42) 3 SwiftUI 0x181e58 OUTLINED_FUNCTION_1147 + 1244 4 myapp 0x7b35c BasePurchaiseScreen.body.getter + 53 (BasePurchaiseScreen.swift:53) 5 myapp 0x7cad8 protocol witness for View.body.getter in conformance BasePurchaiseScreen + 4366256856 (:4366256856) 6 SwiftUI 0x202528 dynamic_cast_existential_0_superclass_conditional + 24428 7 SwiftUI 0x24d738 -[UIScrollView(SwiftUI) _swiftui_adjustsContentInsetWhenScrollDisabled] + 31980 8 SwiftUI 0x24b3bc -[UIScrollView(SwiftUI) _swiftui_adjustsContentInsetWhenScrollDisabled] + 22896 9 SwiftUI 0x1fdb30 dynamic_cast_existential_0_superclass_conditional + 5492 10 AttributeGraph 0x9010 AG::Graph::UpdateStack::update() + 512 11 AttributeGraph 0x8bfc AG::Graph::update_attribute(AG::data::ptr, unsigned int) + 424 12 AttributeGraph 0x2cc0 AG::Graph::input_value_ref_slow(AG::data::ptr, AG::AttributeID, unsigned int, unsigned int, AGSwiftMetadata const*, unsigned char&, long) + 720 ....

I also found "MultiPlatformLibrarySwift/myapp_shared.swift:37: Fatal error: UIStateKs not synchronized with UIState class" message in logs from generated UIStateKs.

Any idea to fix this crash?

Alex009 commented 2 weeks ago

your generated swift code outdated. in kotlin source you have some new cases in sealed, but in swift code this cases not used yet

aleksey-ho commented 2 weeks ago

@Alex009, Hi, Thank you for quick reply! Here's my sealed class UIState and UserPremium used in MutableStateFlow<UIState>(UIState.Empty)

sealed class UIState<out T> {
    object Loading : UIState<Nothing>()
    object Empty : UIState<Nothing>()
    data class Data<T>(val value: T) : UIState<T>()
    data class Error(val throwable: Throwable) : UIState<Nothing>()
}

data class UserPremium(val isActive: Boolean, val state: SubscriptionState)

enum class SubscriptionState {
    AVAILABLE, BILLING_ISSUE, CANCELLED, ERROR
}

and generated UIStateKs:

public enum UIStateKs<T : AnyObject> {

  case data(UIStateData<T>)
  case empty
  case error(UIStateError)
  case loading

  public var sealed: UIState<T> {
    switch self {
    case .data(let obj):
      return obj as MultiPlatformLibrary.UIState<T>
    case .empty:
      return MultiPlatformLibrary.UIStateEmpty() as! MultiPlatformLibrary.UIState<T>
    case .error(let obj):
      return obj as! MultiPlatformLibrary.UIState<T>
    case .loading:
      return MultiPlatformLibrary.UIStateLoading() as! MultiPlatformLibrary.UIState<T>
    }
  }

  public init(_ obj: UIState<T>) {
    if let obj = obj as? MultiPlatformLibrary.UIStateData<T> {
      self = .data(obj)
    } else if obj is MultiPlatformLibrary.UIStateEmpty {
      self = .empty
    } else if let obj = obj as? MultiPlatformLibrary.UIStateError {
      self = .error(obj)
    } else if obj is MultiPlatformLibrary.UIStateLoading {
      self = .loading
    } else {
      fatalError("UIStateKs not synchronized with UIState class")
    }
  }

}

is there anything I need to change? I's not clear for me why is it working on simulator but not in festflight build

Alex009 commented 2 weeks ago

strange. what value you have in UIState<T> when fatalError throws?

aleksey-ho commented 1 week ago

Unfortunately, I can't get the value in debug environment. It crashes only in testflight build

Снимок экрана 2024-07-03 в 11 33 47