@callstack/react-native-brownfield 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
- <p align="center">
2
- <img alt="React Native Brownfield" src="https://user-images.githubusercontent.com/7837457/63594721-7f7b8800-c5b7-11e9-87ef-a0898cd22fcc.png" width="600">
3
- </p>
1
+ <a href="https://www.callstack.com/open-source?utm_campaign=generic&utm_source=github&utm_medium=referral&utm_content=react-native-brownfield" align="center">
2
+ <img alt="React Native Brownfield" src="https://github.com/user-attachments/assets/55fcdff5-54f0-4081-adf6-55dfa5c29af2">
3
+ </a>
4
4
 
5
5
  <p align="center">
6
- Set of helpers to make your brownfield integration smooth and easy.
6
+ A set of helpers to make your brownfield integration smooth and easy.
7
7
  </p>
8
8
 
9
9
  ---
@@ -21,86 +21,199 @@
21
21
 
22
22
  ## Features
23
23
 
24
- - **Easily integrate** React Native with existing native app
24
+ - **Easily integrate** React Native with an existing native app
25
25
  - Start React Native with **one method** and invoke code as soon as it's loaded
26
- - Compatible with **both old and new React Native architecture**!
26
+ - Compatible with **both legacy and new React Native architecture**!
27
27
  - Reuse the same instance of React Native between different components
28
28
  - Use predefined **native building blocks** - crafted for React Native
29
29
  - Disable and enable **native gestures and hardware buttons** from JavaScript
30
- - Works well with **any native navigation** pattern, as well as every React Native JavaScript based navigation
30
+ - Works well with **any native navigation** pattern, as well as any React Native JavaScript-based navigation
31
31
  - Compatible with all native languages **Objective-C**, **Swift**, **Java** and **Kotlin**
32
32
  - Supports UIKit and SwiftUI on iOS and Fragments and Jetpack Compose on Android
33
33
 
34
-
35
34
  ## Installation
36
35
 
36
+ The React Native Brownfield library is intended to be installed in a React Native app that is later consumed as a framework artifact by your native iOS or Android app.
37
+
38
+ In your React Native project run:
39
+
37
40
  ```sh
38
41
  npm install @callstack/react-native-brownfield
39
42
  ```
40
43
 
41
- or
44
+ ## Usage
42
45
 
43
- ```sh
44
- yarn add @callstack/react-native-brownfield
46
+ <a href="https://www.callstack.com/ebooks/incremental-react-native-adoption-in-native-apps?utm_campaign=brownfield&utm_source=github&utm_medium=referral&utm_content=react-native-brownfield" align="center">
47
+ <img alt="Download a free copy of Incremental React Native adoption in native apps ebook" src="https://github.com/user-attachments/assets/ba42bb29-1e7a-4683-80c5-2602afb1a7e6">
48
+ </a>
49
+
50
+ ### Packaging React Native app as a framework
51
+
52
+ First, we need to package our React Native app as an XCFramework or Fat-AAR.
53
+
54
+ #### With RNEF
55
+
56
+ Follow [Integrating with Native Apps](https://www.rnef.dev/docs/brownfield/intro) steps in RNEF docs and run:
57
+
58
+ - `rnef package:ios` for iOS
59
+ - `rnef package:aar` for Android
60
+
61
+ #### With custom scripts
62
+
63
+ Instead of using RNEF, you can create your own custom packaging scripts. Here are base versions for iOS and Android that you'll need to adjust for your project-specific setup:
64
+
65
+ - [Example iOS script](https://github.com/callstackincubator/modern-brownfield-ref/blob/main/scripts/build-xcframework.sh)
66
+ - [Example Android script](https://github.com/callstackincubator/modern-brownfield-ref/blob/main/scripts/build-aar.sh)
67
+
68
+ ### Native iOS app
69
+
70
+ In your native iOS app, initialize React Native and display it where you like. For example, to display React Native views in SwiftUI, use the provided `ReactNativeView` component:
71
+
72
+ ```swift
73
+ import SwiftUI
74
+ import ReactBrownfield # exposed by RN app framework
75
+
76
+ @main
77
+ struct MyApp: App {
78
+ init() {
79
+ ReactNativeBrownfield.shared.startReactNative {
80
+ print("React Native bundle loaded")
81
+ }
82
+ }
83
+
84
+ var body: some Scene {
85
+ WindowGroup {
86
+ ContentView()
87
+ }
88
+ }
89
+ }
90
+
91
+ struct ContentView: View {
92
+ var body: some View {
93
+ NavigationView {
94
+ VStack {
95
+ Text("Welcome to the Native App")
96
+ .padding()
97
+
98
+ NavigationLink("Push React Native Screen") {
99
+ ReactNativeView(moduleName: "ReactNative")
100
+ .navigationBarHidden(true)
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
45
106
  ```
46
107
 
47
- ## Enabling New Architecture
108
+ For more detailed instructions and API for iOS, see docs for:
109
+
110
+ - [Objective C](docs/OBJECTIVE_C.md)
111
+ - [Swift](docs/SWIFT.md)
112
+
113
+ ### Native Android app
48
114
 
49
- ### Android
50
- Add the following to your `android/gradle.properties`:
115
+ In your native Android app, create a new `RNAppFragment.kt`:
51
116
 
117
+ ```kt
118
+
119
+ import android.os.Bundle
120
+ import android.view.LayoutInflater
121
+ import android.view.View
122
+ import android.view.ViewGroup
123
+ import androidx.fragment.app.Fragment
124
+ import com.callstack.rnbrownfield.RNViewFactory # exposed by RN app framework
125
+
126
+ class RNAppFragment : Fragment() {
127
+ override fun onCreateView(
128
+ inflater: LayoutInflater,
129
+ container: ViewGroup?,
130
+ savedInstanceState: Bundle?,
131
+ ): View? =
132
+ this.context?.let {
133
+ RNViewFactory.createFrameLayout(it)
134
+ }
135
+ }
52
136
  ```
53
- # Enable new architecture
54
- newArchEnabled=true
137
+
138
+ Add a button to your `activity_main.xml`:
139
+
140
+ ```xml
141
+ <Button
142
+ android:id="@+id/show_rn_app_btn"
143
+ android:layout_width="wrap_content"
144
+ android:layout_height="wrap_content"
145
+ android:text="Show RN App"
146
+ app:layout_constraintBottom_toBottomOf="parent"
147
+ app:layout_constraintEnd_toEndOf="parent"
148
+ app:layout_constraintStart_toStartOf="parent"
149
+ app:layout_constraintTop_toTopOf="parent" />
55
150
  ```
56
151
 
57
- ### iOS
58
- Install cocoapods with the flag:
152
+ Add a fragment container:
59
153
 
60
- ```
61
- RCT_NEW_ARCH_ENABLED=1 pod install
154
+ ```xml
155
+ <FrameLayout
156
+ android:id="@+id/fragmentContainer"
157
+ android:layout_width="match_parent"
158
+ android:layout_height="match_parent" />
62
159
  ```
63
160
 
64
- > [!NOTE]
65
- > New Architecture is enabled by default from React Native 0.76
161
+ Update your `MainActivity` to initialize React Native and show the fragment:
66
162
 
67
- ## Usage
163
+ ```kt
164
+ class MainActivity : AppCompatActivity() {
165
+ private lateinit var showRNAppBtn: Button
166
+
167
+ override fun onCreate(savedInstanceState: Bundle?) {
168
+ super.onCreate(savedInstanceState)
169
+ ReactNativeHostManager.shared.initialize(this.application)
170
+
171
+ showRNAppBtn = findViewById(R.id.show_rn_app_btn)
172
+ showRNAppBtn.setOnClickListener {
173
+ supportFragmentManager
174
+ .beginTransaction()
175
+ .replace(R.id.fragmentContainer, RNAppFragment())
176
+ .commit()
177
+ }
178
+ }
179
+
180
+ }
181
+ ```
68
182
 
69
- React Native Brownfield library works with all major native programming languages. Majority of its API is exposed on the native side. Click on the logo to choose the one that interests you:
183
+ For more detailed instructions and API for Android, see docs for:
70
184
 
71
- | [<img src="https://user-images.githubusercontent.com/7837457/63374769-cafd1e80-c38a-11e9-9724-e797a199ebab.png" width="100px;" alt="Objective-C"/><br /><sub><b>Objective-C</b></sub>](docs/OBJECTIVE_C.md) | [<img src="https://user-images.githubusercontent.com/7837457/63374778-ce90a580-c38a-11e9-8f37-72a9a5b9a52f.png" width="100px;" alt="Swift"/><br /><sub><b>Swift</b></sub>](docs/SWIFT.md) | [<img src="https://user-images.githubusercontent.com/7837457/63374794-d2bcc300-c38a-11e9-9a7f-d538563b75db.png" width="100px;" alt="Java"/><br /><sub><b>Java</b></sub>](docs/JAVA.md) | [<img src="https://user-images.githubusercontent.com/7837457/63374783-d0f2ff80-c38a-11e9-9790-041cad53b259.png" width="100px;" alt="Kotlin"/><br /><sub><b>Kotlin</b></sub>](docs/KOTLIN.md) |
72
- | :---: | :---: | :---: | :---: |
185
+ - [Java](docs/JAVA.md)
186
+ - [Kotlin](docs/KOTLIN.md)
73
187
 
74
188
  ### JavaScript Module
75
189
 
76
- Besides native components, we are exposing JavaScript functions to control the behavior of those components.
190
+ Besides native components, we are exposing JavaScript functions to control the behavior of those components from React Native app.
77
191
 
78
- To use the module, simply import it:
192
+ To use the module, import it:
79
193
 
80
194
  ```js
81
195
  import ReactNativeBrownfield from '@callstack/react-native-brownfield';
82
196
  ```
83
197
 
84
- ### JavaScript API Reference:
198
+ and use the available methods:
85
199
 
86
- **setNativeBackGestureAndButtonEnabled(enabled: boolean)**
200
+ #### setNativeBackGestureAndButtonEnabled(enabled: boolean)
87
201
 
88
- A method used to toggle iOS native back gesture and Android hardware back button.
202
+ A method used to toggle iOS native back gesture and Android hardware back button.
89
203
 
90
- ```js
204
+ ```ts
91
205
  ReactNativeBrownfield.setNativeBackGestureAndButtonEnabled(true);
92
206
  ```
93
207
 
94
- **popToNative(animated[iOS only]: boolean)**
208
+ #### popToNative(animated[iOS only]: boolean)
95
209
 
96
- A method to pop to native screen used to push React Native experience.
210
+ A method to pop to native screen used to push React Native experience.
97
211
 
98
- ```js
212
+ ```ts
99
213
  ReactNativeBrownfield.popToNative(true);
100
214
  ```
101
215
 
102
- > NOTE: Those methods works only with native components provided by this library.
103
-
216
+ > **Note:** These methods work only with native components provided by this library.
104
217
 
105
218
  ## Made with ❤️ at Callstack
106
219
 
@@ -122,6 +235,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
122
235
  This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
123
236
 
124
237
  <!-- badges -->
238
+
125
239
  [build-badge]: https://img.shields.io/circleci/build/github/callstack/react-native-brownfield/master.svg?style=flat-square
126
240
  [build]: https://circleci.com/gh/callstack/react-native-brownfield
127
241
  [version-badge]: https://img.shields.io/npm/v/@callstack/react-native-brownfield.svg?style=flat-square
@@ -1,3 +1,15 @@
1
+ import groovy.json.JsonSlurper
2
+
3
+ def reactNativeVersion = null
4
+ def packageJsonFile = rootProject.file("../node_modules/react-native/package.json")
5
+
6
+ if (packageJsonFile.exists()) {
7
+ def json = new JsonSlurper().parse(packageJsonFile)
8
+ reactNativeVersion = json.version
9
+ } else {
10
+ reactNativeVersion = "unknown"
11
+ }
12
+
1
13
  buildscript {
2
14
  // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
15
  def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["RNBrownfield_kotlinVersion"]
@@ -64,6 +76,7 @@ android {
64
76
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
65
77
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
66
78
  buildConfigField "boolean", "IS_HERMES_ENABLED", isHermesEnabled().toString()
79
+ buildConfigField "String", "RN_VERSION", "\"$reactNativeVersion\""
67
80
  }
68
81
 
69
82
  buildFeatures {
@@ -0,0 +1,31 @@
1
+ package com.callstack.reactnativebrownfield
2
+
3
+ import android.os.Bundle
4
+ import androidx.activity.ComponentActivity
5
+ import com.facebook.react.ReactDelegate
6
+ import com.facebook.react.ReactHost
7
+ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
8
+
9
+ class ReactDelegateWrapper(
10
+ private val activity: ComponentActivity?,
11
+ private val reactHost: ReactHost,
12
+ moduleName: String,
13
+ launchOptions: Bundle?
14
+ ): ReactDelegate(activity, reactHost, moduleName, launchOptions){
15
+ private lateinit var hardwareBackHandler: () -> Unit
16
+ private val backBtnHandler = DefaultHardwareBackBtnHandler {
17
+ hardwareBackHandler()
18
+ }
19
+
20
+ /**
21
+ * This is invoked when there is no more RN Stack to pop.
22
+ * What it means that this is now the initial RN screen.
23
+ */
24
+ fun setHardwareBackHandler(backHandler: () -> Unit) {
25
+ hardwareBackHandler = backHandler
26
+ }
27
+
28
+ override fun onHostResume() {
29
+ reactHost.onHostResume(activity, backBtnHandler)
30
+ }
31
+ }
@@ -4,10 +4,11 @@ import android.app.Application
4
4
  import android.content.Context
5
5
  import android.os.Bundle
6
6
  import android.widget.FrameLayout
7
+ import androidx.activity.OnBackPressedCallback
7
8
  import androidx.fragment.app.FragmentActivity
8
9
  import androidx.lifecycle.DefaultLifecycleObserver
9
10
  import androidx.lifecycle.LifecycleOwner
10
- import com.facebook.react.ReactDelegate
11
+ import com.callstack.reactnativebrownfield.utils.VersionUtils
11
12
  import com.facebook.react.ReactInstanceEventListener
12
13
  import com.facebook.react.ReactInstanceManager
13
14
  import com.facebook.react.ReactNativeHost
@@ -21,10 +22,17 @@ import java.util.concurrent.atomic.AtomicBoolean
21
22
  import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
22
23
  import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
23
24
 
24
- interface InitializedCallback {
25
+ fun interface OnJSBundleLoaded {
25
26
  operator fun invoke(initialized: Boolean)
26
27
  }
27
28
 
29
+ /**
30
+ * The threshold RN version based on which we decide whether to
31
+ * load JNI libs or not. We only load JNI libs on version less
32
+ * than this.
33
+ */
34
+ private const val RN_THRESHOLD_VERSION = "0.80.0"
35
+
28
36
  class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNativeHost) {
29
37
  companion object {
30
38
  private lateinit var instance: ReactNativeBrownfield
@@ -33,16 +41,34 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative
33
41
  @JvmStatic
34
42
  val shared: ReactNativeBrownfield get() = instance
35
43
 
44
+ private fun loadNativeLibs (application: Application) {
45
+ val rnVersion = BuildConfig.RN_VERSION
46
+
47
+ if (VersionUtils.isVersionLessThan(rnVersion, RN_THRESHOLD_VERSION)) {
48
+ SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping)
49
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
50
+ // If you opted-in for the New Architecture, we load the native entry point for this app.
51
+ load()
52
+ }
53
+ }
54
+ }
55
+
36
56
  @JvmStatic
37
- fun initialize(application: Application, rnHost: ReactNativeHost) {
57
+ @JvmOverloads
58
+ fun initialize(application: Application, rnHost: ReactNativeHost, onJSBundleLoaded: OnJSBundleLoaded? = null) {
38
59
  if (!initialized.getAndSet(true)) {
60
+ loadNativeLibs(application)
39
61
  instance = ReactNativeBrownfield(rnHost)
40
- SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping)
62
+
63
+ preloadReactNative {
64
+ onJSBundleLoaded?.invoke(true)
65
+ }
41
66
  }
42
67
  }
43
68
 
44
69
  @JvmStatic
45
- fun initialize(application: Application, options: HashMap<String, Any>) {
70
+ @JvmOverloads
71
+ fun initialize(application: Application, options: HashMap<String, Any>, onJSBundleLoaded: OnJSBundleLoaded? = null) {
46
72
  val reactNativeHost: ReactNativeHost =
47
73
  object : DefaultReactNativeHost(application) {
48
74
 
@@ -61,37 +87,27 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative
61
87
  override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
62
88
  }
63
89
 
64
- initialize(application, reactNativeHost)
90
+ initialize(application, reactNativeHost, onJSBundleLoaded)
65
91
  }
66
92
 
67
93
  @JvmStatic
68
- fun initialize(application: Application, packages: List<ReactPackage>) {
94
+ @JvmOverloads
95
+ fun initialize(application: Application, packages: List<ReactPackage>, onJSBundleLoaded: OnJSBundleLoaded? = null) {
69
96
  val options = hashMapOf("packages" to packages, "mainModuleName" to "index")
70
97
 
71
- initialize(application, options)
98
+ initialize(application, options, onJSBundleLoaded)
72
99
  }
73
100
 
74
-
75
- }
76
-
77
- fun startReactNative(callback: InitializedCallback?) {
78
- startReactNative { callback?.invoke(it) }
79
- }
80
-
81
- @JvmName("startReactNativeKotlin")
82
- fun startReactNative(callback: ((initialized: Boolean) -> Unit)?) {
83
- reactNativeHost.reactInstanceManager.addReactInstanceEventListener(object :
84
- ReactInstanceEventListener {
85
- override fun onReactContextInitialized(reactContext: ReactContext) {
86
- callback?.let { it(true) }
87
- reactNativeHost.reactInstanceManager.removeReactInstanceEventListener(this)
88
- }
89
- })
90
- reactNativeHost.reactInstanceManager?.createReactContextInBackground()
91
-
92
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
93
- // If you opted-in for the New Architecture, we load the native entry point for this app.
94
- load()
101
+ private fun preloadReactNative(callback: ((Boolean) -> Unit)) {
102
+ val reactInstanceManager = shared.reactNativeHost.reactInstanceManager
103
+ reactInstanceManager.addReactInstanceEventListener(object :
104
+ ReactInstanceEventListener {
105
+ override fun onReactContextInitialized(reactContext: ReactContext) {
106
+ callback(true)
107
+ reactInstanceManager.removeReactInstanceEventListener(this)
108
+ }
109
+ })
110
+ reactInstanceManager?.createReactContextInBackground()
95
111
  }
96
112
  }
97
113
 
@@ -99,6 +115,7 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative
99
115
  context: Context,
100
116
  activity: FragmentActivity?,
101
117
  moduleName: String,
118
+ reactDelegate: ReactDelegateWrapper? = null,
102
119
  launchOptions: Bundle? = null,
103
120
  ): FrameLayout {
104
121
  if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
@@ -106,25 +123,36 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative
106
123
  context,
107
124
  shared.reactNativeHost
108
125
  )
109
- val reactDelegate = ReactDelegate(activity, reactHost, moduleName, launchOptions)
110
126
 
111
- activity?.lifecycle?.addObserver(object : DefaultLifecycleObserver {
112
- override fun onResume(owner: LifecycleOwner) {
113
- reactDelegate.onHostResume()
114
- }
127
+ val resolvedDelegate = reactDelegate ?: ReactDelegateWrapper(activity, reactHost, moduleName, launchOptions)
115
128
 
116
- override fun onPause(owner: LifecycleOwner) {
117
- reactDelegate.onHostPause()
129
+ val mBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
130
+ override fun handleOnBackPressed() {
131
+ // invoked for JS stack back navigation
132
+ resolvedDelegate.onBackPressed()
118
133
  }
134
+ }
119
135
 
120
- override fun onDestroy(owner: LifecycleOwner) {
121
- reactDelegate.onHostDestroy()
122
- owner.lifecycle.removeObserver(this) // Cleanup to avoid leaks
123
- }
124
- })
136
+ // Register back press callback
137
+ activity?.onBackPressedDispatcher?.addCallback(mBackPressedCallback)
138
+ // invoked on the last RN screen exit
139
+ resolvedDelegate.setHardwareBackHandler {
140
+ mBackPressedCallback.isEnabled = false
141
+ activity?.onBackPressedDispatcher?.onBackPressed()
142
+ }
143
+
144
+ /**
145
+ * When createView method is called in ReactNativeFragment, a reactDelegate
146
+ * instance is required. In such a case, we use the lifeCycle events of the fragment.
147
+ * When createView method is called elsewhere, then reactDelegate is not required.
148
+ * In such a case, we set the lifeCycle observer.
149
+ */
150
+ if (reactDelegate == null) {
151
+ activity?.lifecycle?.addObserver(getLifeCycleObserver(resolvedDelegate))
152
+ }
125
153
 
126
- reactDelegate.loadApp()
127
- return reactDelegate.reactRootView!!
154
+ resolvedDelegate.loadApp()
155
+ return resolvedDelegate.reactRootView!!
128
156
  }
129
157
 
130
158
  val instanceManager: ReactInstanceManager? = shared.reactNativeHost?.reactInstanceManager
@@ -137,5 +165,22 @@ class ReactNativeBrownfield private constructor(val reactNativeHost: ReactNative
137
165
 
138
166
  return reactView
139
167
  }
168
+
169
+ private fun getLifeCycleObserver(reactDelegate: ReactDelegateWrapper): DefaultLifecycleObserver {
170
+ return object : DefaultLifecycleObserver {
171
+ override fun onResume(owner: LifecycleOwner) {
172
+ reactDelegate.onHostResume()
173
+ }
174
+
175
+ override fun onPause(owner: LifecycleOwner) {
176
+ reactDelegate.onHostPause()
177
+ }
178
+
179
+ override fun onDestroy(owner: LifecycleOwner) {
180
+ reactDelegate.onHostDestroy()
181
+ owner.lifecycle.removeObserver(this) // Cleanup to avoid leaks
182
+ }
183
+ }
184
+ }
140
185
  }
141
186
 
@@ -3,17 +3,19 @@ package com.callstack.reactnativebrownfield;
3
3
  import android.annotation.TargetApi
4
4
  import android.os.Build
5
5
  import android.os.Bundle
6
+ import android.util.Log
6
7
  import android.view.KeyEvent
8
+ import android.view.LayoutInflater
9
+ import android.view.View
10
+ import android.view.ViewGroup
7
11
  import com.facebook.infer.annotation.Assertions
8
12
  import com.facebook.react.ReactFragment
9
13
  import com.facebook.react.ReactHost
10
14
  import com.facebook.react.ReactNativeHost
11
15
  import com.facebook.react.bridge.Callback
12
16
  import com.facebook.react.bridge.WritableMap
13
- import com.facebook.react.common.LifecycleState
14
17
  import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
15
18
  import com.facebook.react.devsupport.DoubleTapReloadRecognizer
16
- import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
17
19
  import com.facebook.react.modules.core.PermissionAwareActivity
18
20
  import com.facebook.react.modules.core.PermissionListener
19
21
 
@@ -21,12 +23,40 @@ class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
21
23
  private lateinit var doubleTapReloadRecognizer: DoubleTapReloadRecognizer
22
24
  private lateinit var permissionsCallback: Callback
23
25
  private var permissionListener: PermissionListener? = null
26
+ private lateinit var moduleName: String
24
27
 
25
28
  override fun onCreate(savedInstanceState: Bundle?) {
26
- super.onCreate(savedInstanceState)
29
+ /**
30
+ * ReactFragment.onCreate will throw an exception if we do not provide arg_component_name as arguments.
31
+ * We silently catch this exception. The reason is we want to invoke the super<Fragment>.onCreate in
32
+ * ReactFragment. Then initialise the mReactDelegate with ReactDelegateWrapper instead of ReactDelegate.
33
+ *
34
+ * So we purposely force ReactFragment.onCreate to throw an exception, so that we can provide our own
35
+ * implementation for mReactDelegate: ReactDelegateWrapper
36
+ */
37
+ try{
38
+ super.onCreate(savedInstanceState)
39
+ } catch (e: IllegalStateException){
40
+ Log.w("ReactNativeFragment", "ReactFragment threw due to missing arg_component_name: ${e.message} - This is an expected behaviour.")
41
+ }
42
+
43
+ moduleName = arguments?.getString(ARG_MODULE_NAME)!!
44
+ this.mReactDelegate = this.reactHost?.let {
45
+ ReactDelegateWrapper(activity,
46
+ it, moduleName, arguments?.getBundle("arg_launch_options"))
47
+ }
48
+
27
49
  doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
28
50
  }
29
51
 
52
+ override fun onCreateView(
53
+ inflater: LayoutInflater,
54
+ container: ViewGroup?,
55
+ savedInstanceState: Bundle?
56
+ ): View {
57
+ return ReactNativeBrownfield.shared.createView(this.requireContext(), activity, moduleName, this.mReactDelegate as ReactDelegateWrapper)
58
+ }
59
+
30
60
  override fun getReactHost(): ReactHost? {
31
61
  return activity?.let {
32
62
  getDefaultReactHost(
@@ -40,36 +70,6 @@ class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
40
70
  return ReactNativeBrownfield.shared.reactNativeHost
41
71
  }
42
72
 
43
- override fun onResume() {
44
- super.onResume()
45
- if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
46
- ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager?.onHostResume(
47
- activity,
48
- activity as DefaultHardwareBackBtnHandler
49
- )
50
- }
51
- }
52
-
53
- override fun onPause() {
54
- super.onPause()
55
- if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
56
- ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager?.onHostPause(
57
- activity
58
- )
59
- }
60
- }
61
-
62
- override fun onDestroy() {
63
- super.onDestroy()
64
- if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
65
- val reactInstanceMgr = ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager
66
-
67
- if (reactInstanceMgr.lifecycleState != LifecycleState.RESUMED) {
68
- reactInstanceMgr.onHostDestroy(activity)
69
- }
70
- }
71
- }
72
-
73
73
  override fun onRequestPermissionsResult(
74
74
  requestCode: Int,
75
75
  permissions: Array<String>,
@@ -118,22 +118,16 @@ class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
118
118
  .didDoubleTapR(keyCode, it)
119
119
  }
120
120
  if (didDoubleTapR == true) {
121
- ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager.devSupportManager.handleReloadJS()
121
+ reactDelegate.reload()
122
122
  handled = true
123
123
  }
124
124
  }
125
125
  return handled
126
126
  }
127
127
 
128
- fun onBackPressed(backBtnHandler: DefaultHardwareBackBtnHandler) {
129
- if (ReactNativeBrownfieldModule.shouldPopToNative) {
130
- backBtnHandler.invokeDefaultOnBackPressed()
131
- } else if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
132
- ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager.onBackPressed()
133
- }
134
- }
135
-
136
128
  companion object {
129
+ private const val ARG_MODULE_NAME = "arg_module_name"
130
+
137
131
  @JvmStatic
138
132
  @JvmOverloads
139
133
  fun createReactNativeFragment(
@@ -142,7 +136,7 @@ class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
142
136
  ): ReactNativeFragment {
143
137
  val fragment = ReactNativeFragment()
144
138
  val args = Bundle()
145
- args.putString(ARG_COMPONENT_NAME, moduleName)
139
+ args.putString(ARG_MODULE_NAME, moduleName)
146
140
  if (initialProps != null) {
147
141
  args.putBundle(ARG_LAUNCH_OPTIONS, initialProps)
148
142
  }
@@ -0,0 +1,17 @@
1
+ package com.callstack.reactnativebrownfield.utils
2
+
3
+ object VersionUtils {
4
+ fun isVersionLessThan(version: String, threshold: String): Boolean {
5
+ val versionParts = version.split(".").map { it.toIntOrNull() ?: 0 }
6
+ val thresholdParts = threshold.split(".").map { it.toIntOrNull() ?: 0 }
7
+
8
+ val maxLength = maxOf(versionParts.size, thresholdParts.size)
9
+ for (i in 0 until maxLength) {
10
+ val vPart = versionParts.getOrNull(i) ?: 0
11
+ val tPart = thresholdParts.getOrNull(i) ?: 0
12
+ if (vPart != tPart) return vPart < tPart
13
+ }
14
+
15
+ return false // equal versions are not less than
16
+ }
17
+ }
@@ -6,6 +6,7 @@ internal import ReactAppDependencyProvider
6
6
  class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate {
7
7
  var entryFile = "index"
8
8
  var bundlePath = "main.jsbundle"
9
+ var bundle = Bundle.main
9
10
  // MARK: - RCTReactNativeFactoryDelegate Methods
10
11
 
11
12
  override func sourceURL(for bridge: RCTBridge) -> URL? {
@@ -21,7 +22,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate {
21
22
  let resourceName = withoutLast.joined()
22
23
  let fileExtension = resourceURLComponents.last ?? ""
23
24
 
24
- return Bundle.main.url(forResource: resourceName, withExtension: fileExtension)
25
+ return bundle.url(forResource: resourceName, withExtension: fileExtension)
25
26
  #endif
26
27
  }
27
28
  }
@@ -54,6 +55,15 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate {
54
55
  delegate.bundlePath = bundlePath
55
56
  }
56
57
  }
58
+ /**
59
+ * Bundle instance to lookup the JavaScript bundle.
60
+ * Default value: Bundle.main
61
+ */
62
+ @objc public var bundle: Bundle = Bundle.main {
63
+ didSet {
64
+ delegate.bundle = bundle
65
+ }
66
+ }
57
67
  /**
58
68
  * React Native factory instance created when starting React Native.
59
69
  * Default value: nil
@@ -8,7 +8,10 @@ struct ReactNativeViewRepresentable: UIViewControllerRepresentable {
8
8
  var initialProperties: [String: Any] = [:]
9
9
 
10
10
  func makeUIViewController(context: Context) -> UIViewController {
11
- return ReactNativeViewController(moduleName: moduleName)
11
+ return ReactNativeViewController(
12
+ moduleName: moduleName,
13
+ initialProperties: initialProperties
14
+ )
12
15
  }
13
16
 
14
17
  func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callstack/react-native-brownfield",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "license": "MIT",
5
5
  "author": "Michal Chudziak <mike.chudziak@callstack.com>",
6
6
  "contributors": [