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
|
import React
|
||||||
|
|
||||||
@objc(Emitter)
|
@objc(Emitter)
|
||||||
class Emitter: RCTEventEmitter {
|
class Emitter: RCTEventEmitter, MessageEmitter {
|
||||||
|
@Injected private var eventEmitter: EventEmitter
|
||||||
|
|
||||||
override static func requiresMainQueueSetup() -> Bool {
|
override static func requiresMainQueueSetup() -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
EventEmitter.sharedInstance.register(eventEmitter: self)
|
eventEmitter.register(eventEmitter: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func supportedEvents() -> [String]! {
|
override func supportedEvents() -> [String]! {
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,34 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class EventEmitter {
|
protocol MessageEmitter {
|
||||||
static let sharedInstance = EventEmitter()
|
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) {
|
func register(eventEmitter: Emitter) {
|
||||||
self.eventEmitter = eventEmitter
|
self.eventEmitter = eventEmitter
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(message: String) {
|
func send(message: String) {
|
||||||
eventEmitter?.send(message: message)
|
eventEmitter.send(message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isReady: Bool {
|
var isReady: Bool {
|
||||||
return eventEmitter != nil
|
return (eventEmitter is NullMessageEmitter) == false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ import ReactAppDependencyProvider
|
||||||
|
|
||||||
final class SharedState: ObservableObject {
|
final class SharedState: ObservableObject {
|
||||||
var reactNativeFactory: RCTReactNativeFactory?
|
var reactNativeFactory: RCTReactNativeFactory?
|
||||||
private let emitter = EventEmitter.sharedInstance
|
let container = DependencyContainer()
|
||||||
|
|
||||||
// SwiftUI => RN => SwiftUI
|
// SwiftUI => RN => SwiftUI
|
||||||
@Published var message: String = ""
|
@Published var message: String = ""
|
||||||
|
|
||||||
func send(message: String) {
|
init() {
|
||||||
emitter.send(message: message)
|
container.register(EventEmitter.self) { _ in DefaultEventEmitter() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,50 +11,51 @@ import SwiftUI
|
||||||
|
|
||||||
@objc(CustomButton)
|
@objc(CustomButton)
|
||||||
class CustomButton: UIView {
|
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 = {
|
private let button: UIButton = {
|
||||||
let lbl = UILabel()
|
let btn = UIButton(type: .system)
|
||||||
lbl.text = "Hello from native"
|
btn.setTitle("Click Me", for: .normal)
|
||||||
lbl.textColor = .white
|
btn.setTitleColor(.white, for: .normal)
|
||||||
lbl.translatesAutoresizingMaskIntoConstraints = false
|
btn.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return lbl
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let button: UIButton = {
|
override init(frame: CGRect) {
|
||||||
let btn = UIButton(type: .system)
|
super.init(frame: frame)
|
||||||
btn.setTitle("Click Me", for: .normal)
|
self.backgroundColor = .systemBlue
|
||||||
btn.setTitleColor(.white, for: .normal)
|
|
||||||
btn.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return btn
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
self.addSubview(label)
|
||||||
super.init(frame: frame)
|
self.addSubview(button)
|
||||||
self.backgroundColor = .systemBlue
|
|
||||||
|
|
||||||
self.addSubview(label)
|
setupConstraints()
|
||||||
self.addSubview(button)
|
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
setupConstraints()
|
required init?(coder: NSCoder) {
|
||||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
private func setupConstraints() {
|
||||||
fatalError("init(coder:) has not been implemented")
|
NSLayoutConstraint.activate([
|
||||||
}
|
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
|
||||||
|
label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
|
|
||||||
private func setupConstraints() {
|
button.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 12),
|
||||||
NSLayoutConstraint.activate([
|
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
|
button.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -16)
|
||||||
label.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
])
|
||||||
|
}
|
||||||
|
|
||||||
button.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 12),
|
@objc private func buttonTapped() {
|
||||||
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
emitter.send(message: "Clicked in UIButton button that was created in RN")
|
||||||
button.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -16)
|
}
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func buttonTapped() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ import SwiftUI
|
||||||
|
|
||||||
struct ToolboxHeader: View {
|
struct ToolboxHeader: View {
|
||||||
@EnvironmentObject var sharedState: SharedState
|
@EnvironmentObject var sharedState: SharedState
|
||||||
|
@Injected var emitter: EventEmitter
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Actions")
|
Text("Actions")
|
||||||
Button("Make it blue") {
|
Button("Make it blue") {
|
||||||
sharedState.send(message: "hello from Swift!")
|
emitter.send(message: "hello from Swift!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text("Message: \(sharedState.message)")
|
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 window: UIWindow?
|
||||||
|
|
||||||
var reactNativeDelegate: ReactNativeDelegate?
|
var reactNativeDelegate: ReactNativeDelegate?
|
||||||
private let sharedState = SharedState()
|
let sharedState = SharedState()
|
||||||
|
|
||||||
func application(
|
func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue