Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/olive-cloths-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@callstack/react-native-brownfield': minor
---

feat: add method to deallocate reactNativeFactory instance
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ struct ContentView: View {
.navigationBarHidden(true)
.clipShape(RoundedRectangle(cornerRadius: 16))
.background(Color(UIColor.systemBackground))

Button("Stop React Native") {
ReactNativeBrownfield.shared.stopReactNative()
}
.buttonStyle(PlainButtonStyle())
.padding(.top)
.foregroundColor(.red)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(16)
Expand Down
32 changes: 16 additions & 16 deletions apps/RNApp/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PODS:
- boost (1.84.0)
- BrownfieldNavigation (3.0.0):
- BrownfieldNavigation (3.5.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -28,7 +28,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- Brownie (3.0.0):
- Brownie (3.5.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -1838,7 +1838,7 @@ PODS:
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- SocketRocket
- react-native-safe-area-context (5.6.2):
- react-native-safe-area-context (5.7.0):
- boost
- DoubleConversion
- fast_float
Expand All @@ -1856,8 +1856,8 @@ PODS:
- React-graphics
- React-ImageManager
- React-jsi
- react-native-safe-area-context/common (= 5.6.2)
- react-native-safe-area-context/fabric (= 5.6.2)
- react-native-safe-area-context/common (= 5.7.0)
- react-native-safe-area-context/fabric (= 5.7.0)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
Expand All @@ -1868,7 +1868,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-safe-area-context/common (5.6.2):
- react-native-safe-area-context/common (5.7.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -1896,7 +1896,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-safe-area-context/fabric (5.6.2):
- react-native-safe-area-context/fabric (5.7.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2378,7 +2378,7 @@ PODS:
- SocketRocket
- ReactAppDependencyProvider (0.82.1):
- ReactCodegen
- ReactBrownfield (3.0.0):
- ReactBrownfield (3.5.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2494,7 +2494,7 @@ PODS:
- React-perflogger (= 0.82.1)
- React-utils (= 0.82.1)
- SocketRocket
- RNScreens (4.19.0):
- RNScreens (4.24.0):
- boost
- DoubleConversion
- fast_float
Expand All @@ -2521,10 +2521,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNScreens/common (= 4.19.0)
- RNScreens/common (= 4.24.0)
- SocketRocket
- Yoga
- RNScreens/common (4.19.0):
- RNScreens/common (4.24.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2803,8 +2803,8 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
BrownfieldNavigation: 12a34a451661d8f685beebab19d4ba7b43efc409
Brownie: 981350e32e072e5b55b624eb8810ba9bbc9683d9
BrownfieldNavigation: b25cd0c4b253b653743be92d141ffe475e42474d
Brownie: ac5a447e77a9d7713ebdb4e71a6083f00b4364f5
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
FBLazyVector: 0aa6183b9afe3c31fc65b5d1eeef1f3c19b63bfa
Expand Down Expand Up @@ -2844,7 +2844,7 @@ SPEC CHECKSUMS:
React-logger: 500f2fa5697d224e63c33d913c8a4765319e19bf
React-Mapbuffer: 4c50cf6af44286015a20a5995d5321f625c93459
React-microtasksnativemodule: a84b9331106616ab1fa36de9ae555718d4bbdcf5
react-native-safe-area-context: 0a3b034bb63a5b684dd2f5fffd3c90ef6ed41ee8
react-native-safe-area-context: eda63a662750758c1fdd7e719c9f1026c8d161cb
React-NativeModulesApple: efd0906463c79d9b86197dbcf0d58358dff8c5ed
React-oscompat: 95875e81f5d4b3c7b2c888d5bd2c9d83450d8bdb
React-perflogger: 2e229bf33e42c094fd64516d89ec1187a2b79b5b
Expand Down Expand Up @@ -2875,10 +2875,10 @@ SPEC CHECKSUMS:
React-utils: f06ff240e06e2bd4b34e48f1b34cac00866e8979
React-webperformancenativemodule: b3398f8175fa96d992c071b1fa59bd6f9646b840
ReactAppDependencyProvider: a45ef34bb22dc1c9b2ac1f74167d9a28af961176
ReactBrownfield: 03a2fd2f61109c00810b8d82af6f8907095191ed
ReactBrownfield: 9d232f72e023ff2cca3bd6d4de188b2292125c83
ReactCodegen: 0bce2d209e2e802589f4c5ff76d21618200e74cb
ReactCommon: 801eff8cb9c940c04d3a89ce399c343ee3eff654
RNScreens: d6413aeb1878cdafd3c721e2c5218faf5d5d3b13
RNScreens: e902eba58a27d3ad399a495d578e8aba3ea0f490
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 526f25666395d30c297d53154398ffd249eaf9e1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ Starts React Native, produces an instance of React Native. You can use it to ini
}];
```

##### `stopReactNative`

Stops React Native and releases the underlying runtime. Safe to call multiple times. Call it after all React Native views are dismissed.

**Examples:**

```objc
[[ReactNativeBrownfield shared] stopReactNative];
```

##### `view`

Creates a React Native view for the specified module name.
Expand Down
10 changes: 10 additions & 0 deletions docs/docs/docs/api-reference/react-native-brownfield/swift.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ ReactNativeBrownfield.shared.startReactNative(onBundleLoaded: {
})
```

##### `stopReactNative`

Stops React Native and releases the underlying runtime. Safe to call multiple times. Call it after all React Native views are dismissed.

**Examples:**

```swift
ReactNativeBrownfield.shared.stopReactNative()
```

##### `view`

Creates a React Native view for the specified module name.
Expand Down
53 changes: 41 additions & 12 deletions packages/react-native-brownfield/ios/ExpoHostRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ final class ExpoHostRuntime {
private var reactNativeFactory: RCTReactNativeFactory?
private var expoDelegate: ExpoAppDelegate?

private var factory: RCTReactNativeFactory {
if let existingFactory = reactNativeFactory {
return existingFactory
}

delegate.dependencyProvider = RCTAppDependencyProvider()
let createdFactory = ExpoReactNativeFactory(delegate: delegate)
reactNativeFactory = createdFactory
return createdFactory
}

/**
* Starts React Native with default parameters.
*/
Expand Down Expand Up @@ -46,6 +57,24 @@ final class ExpoHostRuntime {
}
}

/**
* Stops React Native and releases the underlying factory instance.
*/
public func stopReactNative() {
if !Thread.isMainThread {
DispatchQueue.main.async { [weak self] in self?.stopReactNative() }
return
}

#if !EXPO_SDK_GTE_55
guard let factory = reactNativeFactory else { return }
factory.bridge?.invalidate()
#endif

reactNativeFactory = nil
expoDelegate = nil
}

/**
* Path to JavaScript root.
* Default value: ".expo/.virtual-metro-entry"
Expand Down Expand Up @@ -125,19 +154,19 @@ final class ExpoHostRuntime {
// below: https://github.com/expo/expo/commit/2013760c46cde1404872d181a691da72fbf207a4
// has moved the recreateRootView method to ExpoReactNativeFactory
#if EXPO_SDK_GTE_55 // this define comes from the Brownfield Expo config plugin
return (reactNativeFactory as? ExpoReactNativeFactory)?.recreateRootView(
withBundleURL: bundleURL,
moduleName: moduleName,
initialProps: initialProps,
launchOptions: launchOptions
)
return (factory as? ExpoReactNativeFactory)?.recreateRootView(
withBundleURL: bundleURL,
moduleName: moduleName,
initialProps: initialProps,
launchOptions: launchOptions
)
#else
return expoDelegate?.recreateRootView(
withBundleURL: bundleURL,
moduleName: moduleName,
initialProps: initialProps,
launchOptions: launchOptions
)
return expoDelegate?.recreateRootView(
withBundleURL: bundleURL,
moduleName: moduleName,
initialProps: initialProps,
launchOptions: launchOptions
)
#endif
}
}
Expand Down
11 changes: 11 additions & 0 deletions packages/react-native-brownfield/ios/ReactNativeBrownfield.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ internal import Expo
#endif
}

/**
* Stops React Native.
*/
@objc public func stopReactNative() {
#if canImport(Expo)
ExpoHostRuntime.shared.stopReactNative()
#else
ReactNativeHostRuntime.shared.stopReactNative()
#endif
}

@objc public func view(
moduleName: String,
initialProps: [AnyHashable: Any]?,
Expand Down
38 changes: 31 additions & 7 deletions packages/react-native-brownfield/ios/ReactNativeHostRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ final class ReactNativeHostRuntime {
delegate.bundlePath = bundlePath
}
}

/**
* Bundle instance to lookup the JavaScript bundle.
* Default value: Bundle.main
Expand All @@ -68,6 +69,7 @@ final class ReactNativeHostRuntime {
delegate.bundle = bundle
}
}

/**
* Dynamic bundle URL provider called on every bundle load.
* When set, this overrides the default bundleURL() behavior in the delegate.
Expand All @@ -79,17 +81,23 @@ final class ReactNativeHostRuntime {
delegate.bundleURLOverride = bundleURLOverride
}
}

/**
* React Native factory instance created when starting React Native.
* Default value: nil
*/
private var reactNativeFactory: RCTReactNativeFactory? = nil
/**
* Root view factory used to create React Native views.
*/
lazy private var rootViewFactory: RCTRootViewFactory? = {
return reactNativeFactory?.rootViewFactory
}()

private var factory: RCTReactNativeFactory {
if let existingFactory = reactNativeFactory {
return existingFactory
}

delegate.dependencyProvider = RCTAppDependencyProvider()
let createdFactory = RCTReactNativeFactory(delegate: delegate)
reactNativeFactory = createdFactory
return createdFactory
}

/**
* Starts React Native with default parameters.
Expand All @@ -98,12 +106,28 @@ final class ReactNativeHostRuntime {
startReactNative(onBundleLoaded: nil)
}

/**
* Stops React Native and releases the underlying factory instance.
*/
public func stopReactNative() {
if !Thread.isMainThread {
DispatchQueue.main.async { [weak self] in self?.stopReactNative() }
return
}

guard let factory = reactNativeFactory else { return }

factory.bridge?.invalidate()

reactNativeFactory = nil
}

public func view(
moduleName: String,
initialProps: [AnyHashable: Any]?,
launchOptions: [AnyHashable: Any]? = nil
) -> UIView? {
rootViewFactory?.view(
factory.rootViewFactory.view(
withModuleName: moduleName,
initialProperties: initialProps,
launchOptions: launchOptions
Expand Down
Loading