Skip to content

Instantly share code, notes, and snippets.

@saket
Last active July 30, 2023 16:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saket/62b1ccfc1a92c393be60a1cfd5357d98 to your computer and use it in GitHub Desktop.
Save saket/62b1ccfc1a92c393be60a1cfd5357d98 to your computer and use it in GitHub Desktop.
SwiftUI with Reaktive presenters written with Kotlin Multiplatform
import SwiftUI
import Combine
import Common // Code shared through Kotlin Multiplaform.
import CombineExt // https://github.com/CombineCommunity/CombineExt
/// A convenience pass-through View to hide away the verbosity
/// of subscribing to a presenter's stream. Usage:
///
/// struct FooView: View {
/// let presenter: FooPresenter
///
/// var body: some View {
/// Present(presenter) { model ->
/// Text(model.name)
/// }
/// }
/// }
struct Present<Content: View, Event: AnyObject, Model: AnyObject>: View {
@State var models: AnyPublisher<Model, Never>
@State var currentModel: Model
private let content: (Model) -> Content
// Presenter is an interface that provides a stream of View models.
public init(
_ presenter: Presenter<Event, Model>,
@ViewBuilder content: @escaping (Model) -> Content
) {
self.content = content
self.currentModel = presenter.initialModel()
self.models = ReaktiveInterop.toCombinePublisher(reaktive: presenter.viewModels())
.assertNoFailure()
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
var body: some View {
// onReceive() will manage the lifecycle of this stream.
content(currentModel).onReceive(models) { model in
self.currentModel = model
}
}
}
class ReaktiveInterop {
static func toCombinePublisher<T>(reaktive: ObservableWrapper<T>) -> AnyPublisher<T, ReaktiveError> {
return AnyPublisher.create { (subscriber: Publishers.Create.Subscriber) in
let reaktiveDisposable = reaktive.subscribe(
isThreadLocal: false,
onSubscribe: nil,
onError: { e in
e.printStackTrace()
subscriber.send(completion: .failure(ReaktiveError(throwable: e)))
},
onComplete: {
subscriber.send(completion: .finished)
},
onNext: { value in
subscriber.send(value)
}
)
return AnyCancellable {
reaktiveDisposable.dispose()
}
}
}
public struct ReaktiveError: Error {
let throwable: KotlinThrowable
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment