https://kotlinlang.org logo
Title
x

xxfast

08/04/2022, 1:26 AM
Has anyone figured out a nice way to bridge a
State
from
StateFlow<State>
to SwiftUi as
@ObservedObject
? Rather than doing
@Published var state: State = State.companion.DEFAULT

viewModel.onState { (state) in
  self.state = state
}
Found this, but has there been any more development on the best approach for this?
h

hfhbd

08/04/2022, 7:00 AM
j

John O'Reilly

08/04/2022, 7:32 AM
@xxfast following more recent article talks about using that KMP-NativeCoroutines library https://johnoreilly.dev/posts/kmp-native-coroutines/
Using that in most samples now and really like it
r

Rick Clephas

08/04/2022, 3:23 PM
Some useful operators for Swift: • https://developer.apple.com/documentation/combine/publisher/assertnofailure(_:file:line:)https://developer.apple.com/documentation/combine/publisher/assign(to:) I would probably go with something like:
@Published var state: State = viewModel.stateNativeValue

createPublisher(for: viewModel.stateNative).assertNoFailure().assign(to: &$state)
Maybe it’s event possible to create a custom property wrapper of it (haven’t tried that yet).
x

xxfast

08/05/2022, 7:44 AM
loving kmp-native-coroutines, the plugin does makes things easier. Though i should say i've been running into this exception getting thrown (im using the
-new-mm
variant
Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: Trying to access top level value not marked as @ThreadLocal or @SharedImmutable from non-main thread
I'll post this one as a seperate thread i think
j

John O'Reilly

08/05/2022, 7:45 AM
You have enabled use of new memory model?
x

xxfast

08/05/2022, 7:58 AM
I derped - forgot to enable this aaand its working 🙌 thanks guys
h

hfhbd

08/05/2022, 7:58 AM
Just use 1.7.20-Beta, it is enabled by default 😄
x

xxfast

08/08/2022, 12:03 AM
Hi all, (again). Just wanted to share my approach in sharing state from a common ViewModel with kmp-native-coroutines. My viewmodel is defined
// commonMain
class LoginViewModel(..) : ViewModel() {
  private val stateFlow: MutableStateFlow<LoginState> = MutableStateFlow(defaultState)

  val states: StateFlow<LoginState> = stateFlow.asStateFlow()
}
on ios, i have
class LoginViewModelDelegate: ObservableObject {

  @Published var state: LoginLoginState = LoginLoginState.companion.DEFAULT
  
  private let viewModel: LoginLoginViewModel = AppModule.LoginModule().viewModel

  private var stateCollection: Task<Void, Error>? = nil

  func collect() {
    stateCollection = Task {
      let states = asyncStream(for: viewModel.statesNative)
      for try await _state in states {
        self.state = _state
      }
    }
  }

  func cancel() {
    stateCollection?.cancel()
  }
}
and the swiftUi view looks like
struct LoginScreen: View {
  @StateObject private var viewModelDelegate = LoginViewModelDelegate()

  var body: some View {
    LoginView(
      state: viewModelDelegate.state,
      onChange: { state in
        viewModelDelegate.onChange(state: state)
      },
      onSubmit: { state in
        viewModelDelegate.onSubmit(state: state)
      }
    )
    .onAppear(perform: viewModelDelegate.collect)
    .onDisappear(perform: viewModelDelegate.cancel)
  }
}
This works, but i dont like it 🤔 Is there a way to avoid having two "ViewModel"s ( view model and the delegate) here?
r

Rick Clephas

08/08/2022, 5:16 AM
Technically you can. Using property wrappers. Here is a POC https://gist.github.com/rickclephas/4c2ab99add21688001168886f759fa50 Though it has a major limitation, you can't access instance members in the propery wrapper constructor. But maybe that isn't needed if you can somehow wrap the StateObject as well.
a

alex009

08/08/2022, 6:07 AM
check this post - i implement viewmodel binding without swift observableobject wrapper
h

hfhbd

08/08/2022, 7:05 AM
Alternative you can use the closure initialer for parameter based StateObject:
struct Login: View {
    init(viewModel: @autoclosure @escaping () -> LoginViewModel) {
        self._viewModel = StateObject(wrappedValue: viewModel())
    }

    @StateObject var viewModel: LoginViewModel

    @State private var error: Failure? = nil
    @State private var disableLogin = true

    var body: some View {
        Form {
            TextField("Username", text: viewModel.binding(\.userName))
            SecureField("Password", text: viewModel.binding(\.password))
And custom bindings to map a flow to Swift.Binding: https://github.com/hfhbd/ComposeTodo/blob/main/iosApp/Shared/ViewModel.swift Based on @alex009
j

John O'Reilly

08/08/2022, 8:20 AM
@xxfast one minor comment about code above.....could you use
.task
view modifier in SwiftUI to call
viewModelDelegate.collect
(which becomes
async
function)....with automatic cancellation then when view disappears (so shouldn't need I think
cancel
?
x

xxfast

08/08/2022, 8:32 AM
Yeah I saw that, xcode told me that it is not supported for my min iOS target 🤔
j

John O'Reilly

08/08/2022, 8:33 AM
but you were still able to use
for await
? Perhaps some differences in iOS versions for those
x

xxfast

08/08/2022, 8:35 AM
Let me check the exact error again
j

John O'Reilly

08/08/2022, 8:38 AM
task
requires iOS15.....I think that was originally case for async/await but I believe that at least got back ported to earlier version.....
x

xxfast

08/08/2022, 8:39 AM
Ah, that explains it
j

John O'Reilly

08/08/2022, 8:40 AM
what iOS version are you targetting?
x

xxfast

08/08/2022, 8:41 AM
14, need to support n-1 :(
j

John O'Reilly

08/08/2022, 8:46 AM
ok, so maybe when iOS 16 comes out soon you can start using iOS 15 stuff 🙂
r

Rick Clephas

08/08/2022, 8:49 AM
task
requires iOS15.....I think that was originally case for async/await but I believe that at least got back ported to earlier version.....
Yeah if I am not mistaken only Swift concurrency has been back ported to iOS 13, SwiftUI and Combine async APIs haven’t.
j

John O'Reilly

08/08/2022, 8:50 AM
It's a pity....the combination of 2 is pretty compelling