Add dependency injection
This commit is contained in:
parent
151ed6d78b
commit
bf94769573
9 changed files with 140 additions and 51 deletions
|
@ -9,14 +9,16 @@ import Combine
|
|||
import React
|
||||
|
||||
@objc(Emitter)
|
||||
class Emitter: RCTEventEmitter {
|
||||
class Emitter: RCTEventEmitter, MessageEmitter {
|
||||
@Injected private var eventEmitter: EventEmitter
|
||||
|
||||
override static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
EventEmitter.sharedInstance.register(eventEmitter: self)
|
||||
eventEmitter.register(eventEmitter: self)
|
||||
}
|
||||
|
||||
override func supportedEvents() -> [String]! {
|
||||
|
|
|
@ -7,22 +7,34 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class EventEmitter {
|
||||
static let sharedInstance = EventEmitter()
|
||||
protocol MessageEmitter {
|
||||
func send(message: String)
|
||||
}
|
||||
|
||||
private var eventEmitter: Emitter?
|
||||
class NullMessageEmitter: MessageEmitter {
|
||||
func send(message: String) {
|
||||
print("Warning: Emiter is not ready yet")
|
||||
}
|
||||
}
|
||||
|
||||
private init() {}
|
||||
|
||||
protocol EventEmitter {
|
||||
func send(message: String)
|
||||
func register(eventEmitter: Emitter)
|
||||
}
|
||||
|
||||
class DefaultEventEmitter: EventEmitter {
|
||||
private var eventEmitter: MessageEmitter = NullMessageEmitter()
|
||||
|
||||
func register(eventEmitter: Emitter) {
|
||||
self.eventEmitter = eventEmitter
|
||||
}
|
||||
|
||||
func send(message: String) {
|
||||
eventEmitter?.send(message: message)
|
||||
eventEmitter.send(message: message)
|
||||
}
|
||||
|
||||
var isReady: Bool {
|
||||
return eventEmitter != nil
|
||||
return (eventEmitter is NullMessageEmitter) == false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ import ReactAppDependencyProvider
|
|||
|
||||
final class SharedState: ObservableObject {
|
||||
var reactNativeFactory: RCTReactNativeFactory?
|
||||
private let emitter = EventEmitter.sharedInstance
|
||||
let container = DependencyContainer()
|
||||
|
||||
// SwiftUI => RN => SwiftUI
|
||||
@Published var message: String = ""
|
||||
|
||||
func send(message: String) {
|
||||
emitter.send(message: message)
|
||||
init() {
|
||||
container.register(EventEmitter.self) { _ in DefaultEventEmitter() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,50 +11,51 @@ import SwiftUI
|
|||
|
||||
@objc(CustomButton)
|
||||
class CustomButton: UIView {
|
||||
@Injected private var emitter: EventEmitter
|
||||
|
||||
private let label: UILabel = {
|
||||
let lbl = UILabel()
|
||||
lbl.text = "Hello from native"
|
||||
lbl.textColor = .white
|
||||
lbl.translatesAutoresizingMaskIntoConstraints = false
|
||||
return lbl
|
||||
}()
|
||||
|
||||
private let label: UILabel = {
|
||||
let lbl = UILabel()
|
||||
lbl.text = "Hello from native"
|
||||
lbl.textColor = .white
|
||||
lbl.translatesAutoresizingMaskIntoConstraints = false
|
||||
return lbl
|
||||
}()
|
||||
private let button: UIButton = {
|
||||
let btn = UIButton(type: .system)
|
||||
btn.setTitle("Click Me", for: .normal)
|
||||
btn.setTitleColor(.white, for: .normal)
|
||||
btn.translatesAutoresizingMaskIntoConstraints = false
|
||||
return btn
|
||||
}()
|
||||
|
||||
private let button: UIButton = {
|
||||
let btn = UIButton(type: .system)
|
||||
btn.setTitle("Click Me", for: .normal)
|
||||
btn.setTitleColor(.white, for: .normal)
|
||||
btn.translatesAutoresizingMaskIntoConstraints = false
|
||||
return btn
|
||||
}()
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = .systemBlue
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = .systemBlue
|
||||
self.addSubview(label)
|
||||
self.addSubview(button)
|
||||
|
||||
self.addSubview(label)
|
||||
self.addSubview(button)
|
||||
setupConstraints()
|
||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
setupConstraints()
|
||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
||||
}
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
private func setupConstraints() {
|
||||
NSLayoutConstraint.activate([
|
||||
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
|
||||
label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
|
||||
private func setupConstraints() {
|
||||
NSLayoutConstraint.activate([
|
||||
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
|
||||
label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
button.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 12),
|
||||
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
button.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -16)
|
||||
])
|
||||
}
|
||||
|
||||
button.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 12),
|
||||
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
button.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -16)
|
||||
])
|
||||
}
|
||||
|
||||
@objc private func buttonTapped() {
|
||||
|
||||
}
|
||||
@objc private func buttonTapped() {
|
||||
emitter.send(message: "Clicked in UIButton button that was created in RN")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,14 @@ import SwiftUI
|
|||
|
||||
struct ToolboxHeader: View {
|
||||
@EnvironmentObject var sharedState: SharedState
|
||||
@Injected var emitter: EventEmitter
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Actions")
|
||||
Button("Make it blue") {
|
||||
sharedState.send(message: "hello from Swift!")
|
||||
emitter.send(message: "hello from Swift!")
|
||||
}
|
||||
}
|
||||
Text("Message: \(sharedState.message)")
|
||||
|
|
47
ios/Native/Utils/DI/DependencyContainer.swift
Normal file
47
ios/Native/Utils/DI/DependencyContainer.swift
Normal file
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// DependencyContainer.swift
|
||||
// Vitaway
|
||||
//
|
||||
// Created by Artur Gurgul on 20/06/2025.
|
||||
//
|
||||
|
||||
final class DependencyContainer: Resolver {
|
||||
private var factories: [String: (Resolver) -> Any] = [:]
|
||||
private var instances: [String: Any] = [:]
|
||||
|
||||
func register<T>(_ type: T.Type, cache: Bool = true, factory: @escaping (Resolver) -> T) {
|
||||
let key = String(describing: type)
|
||||
factories[key] = factory
|
||||
|
||||
if cache {
|
||||
instances[key] = factory(self)
|
||||
}
|
||||
}
|
||||
|
||||
func registerSingleton<T>(_ type: T.Type, factory: @escaping (Resolver) -> T) {
|
||||
let key = String(describing: type)
|
||||
factories[key] = factory
|
||||
}
|
||||
|
||||
func resolve<T>() -> T {
|
||||
resolve(type: T.self)
|
||||
}
|
||||
|
||||
func resolve<T>(type: T.Type) -> T {
|
||||
let key = String(describing: T.self)
|
||||
|
||||
if let instance = instances[key] as? T {
|
||||
return instance
|
||||
}
|
||||
|
||||
guard let factory = factories[key], let instance = factory(self) as? T else {
|
||||
fatalError("No registered entry for type \(key)")
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
static var shared: DependencyContainer {
|
||||
(UIApplication.shared.delegate as! AppDelegate).sharedState.container
|
||||
}
|
||||
}
|
||||
|
16
ios/Native/Utils/DI/Injected.swift
Normal file
16
ios/Native/Utils/DI/Injected.swift
Normal file
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Injected.swift
|
||||
// Vitaway
|
||||
//
|
||||
// Created by Artur Gurgul on 05/07/2025.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@propertyWrapper
|
||||
class Injected<T> {
|
||||
var wrappedValue: T {
|
||||
DependencyContainer.shared.resolve(type: T.self)
|
||||
}
|
||||
|
||||
}
|
10
ios/Native/Utils/DI/Resolver.swift
Normal file
10
ios/Native/Utils/DI/Resolver.swift
Normal file
|
@ -0,0 +1,10 @@
|
|||
//
|
||||
// Resolver.swift
|
||||
// Vitaway
|
||||
//
|
||||
// Created by Artur Gurgul on 20/06/2025.
|
||||
//
|
||||
|
||||
protocol Resolver {
|
||||
func resolve<T>() -> T
|
||||
}
|
|
@ -9,7 +9,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
var window: UIWindow?
|
||||
|
||||
var reactNativeDelegate: ReactNativeDelegate?
|
||||
private let sharedState = SharedState()
|
||||
let sharedState = SharedState()
|
||||
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue