@callstack/react-native-brownfield 1.2.0 → 2.0.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 +17 -12
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactDelegateWrapper.kt +9 -4
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +120 -138
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfieldPackage.kt +10 -11
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeFragment.kt +109 -133
- package/android/src/main/java/com/callstack/reactnativebrownfield/constants/ReactNativeFragmentArgNames.kt +20 -0
- package/android/src/newarch/ReactNativeBrownfieldModule.kt +24 -24
- package/ios/ReactNativeBrownfield.swift +1 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -31,6 +31,13 @@
|
|
|
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
|
+
## React Native version compatibility matrix
|
|
35
|
+
|
|
36
|
+
| Tested React Native Version | React Native Brownfield Version |
|
|
37
|
+
| --------------------------- | ------------------------------- |
|
|
38
|
+
| 0.81.x, 0.82.x | ^2.0.0-rc.0 |
|
|
39
|
+
| 0.78.x | ^1.2.0 |
|
|
40
|
+
|
|
34
41
|
## Installation
|
|
35
42
|
|
|
36
43
|
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.
|
|
@@ -51,16 +58,16 @@ npm install @callstack/react-native-brownfield
|
|
|
51
58
|
|
|
52
59
|
First, we need to package our React Native app as an XCFramework or Fat-AAR.
|
|
53
60
|
|
|
54
|
-
#### With
|
|
61
|
+
#### With Rock
|
|
55
62
|
|
|
56
|
-
Follow [Integrating with Native Apps](https://www.
|
|
63
|
+
Follow [Integrating with Native Apps](https://www.rockjs.dev/docs/brownfield/intro) steps in Rock docs and run:
|
|
57
64
|
|
|
58
|
-
- `
|
|
59
|
-
- `
|
|
65
|
+
- `rock package:ios` for iOS
|
|
66
|
+
- `rock package:aar` for Android
|
|
60
67
|
|
|
61
68
|
#### With custom scripts
|
|
62
69
|
|
|
63
|
-
Instead of using
|
|
70
|
+
Instead of using Rock, 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
71
|
|
|
65
72
|
- [Example iOS script](https://github.com/callstackincubator/modern-brownfield-ref/blob/main/scripts/build-xcframework.sh)
|
|
66
73
|
- [Example Android script](https://github.com/callstackincubator/modern-brownfield-ref/blob/main/scripts/build-aar.sh)
|
|
@@ -121,17 +128,13 @@ import android.view.LayoutInflater
|
|
|
121
128
|
import android.view.View
|
|
122
129
|
import android.view.ViewGroup
|
|
123
130
|
import androidx.fragment.app.Fragment
|
|
124
|
-
import com.callstack.rnbrownfield.RNViewFactory # exposed by RN app framework
|
|
125
131
|
|
|
126
132
|
class RNAppFragment : Fragment() {
|
|
127
133
|
override fun onCreateView(
|
|
128
134
|
inflater: LayoutInflater,
|
|
129
135
|
container: ViewGroup?,
|
|
130
136
|
savedInstanceState: Bundle?,
|
|
131
|
-
): View? =
|
|
132
|
-
this.context?.let {
|
|
133
|
-
RNViewFactory.createFrameLayout(it)
|
|
134
|
-
}
|
|
137
|
+
): View? = ReactNativeBrownfield.shared.createView(activity, "BrownFieldTest")
|
|
135
138
|
}
|
|
136
139
|
```
|
|
137
140
|
|
|
@@ -166,7 +169,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
166
169
|
|
|
167
170
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
168
171
|
super.onCreate(savedInstanceState)
|
|
169
|
-
ReactNativeHostManager.shared.initialize(this.application)
|
|
172
|
+
ReactNativeHostManager.shared.initialize(this.application) {
|
|
173
|
+
println("JS bundle loaded")
|
|
174
|
+
}
|
|
170
175
|
|
|
171
176
|
showRNAppBtn = findViewById(R.id.show_rn_app_btn)
|
|
172
177
|
showRNAppBtn.setOnClickListener {
|
|
@@ -228,7 +233,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
|
|
|
228
233
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
229
234
|
<!-- prettier-ignore -->
|
|
230
235
|
| [<img src="https://avatars0.githubusercontent.com/u/7837457?s=460&v=4" width="100px;" alt="Michał Chudziak"/><br /><sub><b>Michał Chudziak</b></sub>](https://twitter.com/michalchudziak)<br />[💻](https://github.com/callstack/react-native-brownfield/commits?author=michalchudziak "Code") [📖](https://github.com/callstack/react-native-brownfield/commits?author=michalchudziak "Documentation") [🤔](#ideas-michalchudziak "Ideas, Planning, & Feedback") | [<img src="https://avatars1.githubusercontent.com/u/16336501?s=400&v=4" width="100px;" alt="Piotr Drapich"/><br /><sub><b>Piotr Drapich</b></sub>](https://twitter.com/dratwas)<br />[💻](https://github.com/callstack/react-native-brownfield/commits?author=dratwas "Code") [🤔](#ideas-dratwas "Ideas, Planning, & Feedback") |
|
|
231
|
-
|
|
|
236
|
+
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
|
232
237
|
|
|
233
238
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
234
239
|
|
|
@@ -8,10 +8,15 @@ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
|
|
|
8
8
|
|
|
9
9
|
class ReactDelegateWrapper(
|
|
10
10
|
private val activity: ComponentActivity?,
|
|
11
|
-
|
|
11
|
+
resolvedReactHost: ReactHost?,
|
|
12
12
|
moduleName: String,
|
|
13
13
|
launchOptions: Bundle?
|
|
14
|
-
): ReactDelegate(
|
|
14
|
+
) : ReactDelegate(
|
|
15
|
+
activity = activity!!,
|
|
16
|
+
resolvedReactHost,
|
|
17
|
+
appKey = moduleName,
|
|
18
|
+
launchOptions = launchOptions,
|
|
19
|
+
) {
|
|
15
20
|
private lateinit var hardwareBackHandler: () -> Unit
|
|
16
21
|
private val backBtnHandler = DefaultHardwareBackBtnHandler {
|
|
17
22
|
hardwareBackHandler()
|
|
@@ -25,7 +30,7 @@ class ReactDelegateWrapper(
|
|
|
25
30
|
hardwareBackHandler = backHandler
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
reactHost
|
|
33
|
+
fun onReactHostResume() {
|
|
34
|
+
super.reactHost?.onHostResume(activity, backBtnHandler)
|
|
30
35
|
}
|
|
31
36
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.callstack.reactnativebrownfield
|
|
2
2
|
|
|
3
3
|
import android.app.Application
|
|
4
|
-
import android.content.Context
|
|
5
4
|
import android.os.Bundle
|
|
6
5
|
import android.widget.FrameLayout
|
|
7
6
|
import androidx.activity.OnBackPressedCallback
|
|
@@ -9,21 +8,19 @@ import androidx.fragment.app.FragmentActivity
|
|
|
9
8
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
10
9
|
import androidx.lifecycle.LifecycleOwner
|
|
11
10
|
import com.callstack.reactnativebrownfield.utils.VersionUtils
|
|
11
|
+
import com.facebook.react.ReactHost
|
|
12
12
|
import com.facebook.react.ReactInstanceEventListener
|
|
13
|
-
import com.facebook.react.ReactInstanceManager
|
|
14
|
-
import com.facebook.react.ReactNativeHost
|
|
15
13
|
import com.facebook.react.ReactPackage
|
|
16
|
-
import com.facebook.react.ReactRootView
|
|
17
14
|
import com.facebook.react.bridge.ReactContext
|
|
18
|
-
import com.facebook.react.
|
|
15
|
+
import com.facebook.react.common.build.ReactBuildConfig
|
|
16
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
|
17
|
+
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
19
18
|
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
|
20
19
|
import com.facebook.soloader.SoLoader
|
|
21
20
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
22
|
-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
|
23
|
-
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
24
21
|
|
|
25
22
|
fun interface OnJSBundleLoaded {
|
|
26
|
-
|
|
23
|
+
operator fun invoke(initialized: Boolean)
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
/**
|
|
@@ -33,154 +30,139 @@ fun interface OnJSBundleLoaded {
|
|
|
33
30
|
*/
|
|
34
31
|
private const val RN_THRESHOLD_VERSION = "0.80.0"
|
|
35
32
|
|
|
36
|
-
class ReactNativeBrownfield private constructor(val
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
34
|
+
companion object {
|
|
35
|
+
private lateinit var instance: ReactNativeBrownfield
|
|
36
|
+
private val initialized = AtomicBoolean()
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
@JvmStatic
|
|
39
|
+
val shared: ReactNativeBrownfield get() = instance
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
private fun loadNativeLibs(application: Application) {
|
|
42
|
+
val rnVersion = BuildConfig.RN_VERSION
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
load()
|
|
44
|
+
if (VersionUtils.isVersionLessThan(rnVersion, RN_THRESHOLD_VERSION)) {
|
|
45
|
+
SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping)
|
|
46
|
+
load()
|
|
47
|
+
}
|
|
52
48
|
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
@JvmStatic
|
|
51
|
+
@JvmOverloads
|
|
52
|
+
fun initialize(
|
|
53
|
+
application: Application,
|
|
54
|
+
reactHost: ReactHost,
|
|
55
|
+
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
56
|
+
) {
|
|
57
|
+
if (!initialized.getAndSet(true)) {
|
|
58
|
+
loadNativeLibs(application)
|
|
59
|
+
instance = ReactNativeBrownfield(reactHost)
|
|
60
|
+
|
|
61
|
+
preloadReactNative {
|
|
62
|
+
onJSBundleLoaded?.invoke(true)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
@JvmStatic
|
|
68
|
+
@JvmOverloads
|
|
69
|
+
fun initialize(
|
|
70
|
+
application: Application,
|
|
71
|
+
options: HashMap<String, Any>,
|
|
72
|
+
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
73
|
+
) {
|
|
74
|
+
val reactHost: ReactHost by lazy {
|
|
75
|
+
getDefaultReactHost(
|
|
76
|
+
context = application,
|
|
77
|
+
packageList = (options["packages"] as? List<*> ?: emptyList<ReactPackage>())
|
|
78
|
+
.filterIsInstance<ReactPackage>(),
|
|
79
|
+
jsMainModulePath = options["mainModuleName"] as? String ?: "index",
|
|
80
|
+
useDevSupport = options["useDeveloperSupport"] as? Boolean
|
|
81
|
+
?: ReactBuildConfig.DEBUG,
|
|
82
|
+
jsRuntimeFactory = null
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
initialize(application, reactHost, onJSBundleLoaded)
|
|
65
87
|
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
@JvmStatic
|
|
90
|
+
@JvmOverloads
|
|
91
|
+
fun initialize(
|
|
92
|
+
application: Application,
|
|
93
|
+
packages: List<ReactPackage>,
|
|
94
|
+
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
95
|
+
) {
|
|
96
|
+
val options = hashMapOf("packages" to packages, "mainModuleName" to "index")
|
|
74
97
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
98
|
+
initialize(application, options, onJSBundleLoaded)
|
|
99
|
+
}
|
|
78
100
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
private fun preloadReactNative(callback: ((Boolean) -> Unit)) {
|
|
102
|
+
shared.reactHost.addReactInstanceEventListener(object :
|
|
103
|
+
ReactInstanceEventListener {
|
|
104
|
+
override fun onReactContextInitialized(context: ReactContext) {
|
|
105
|
+
callback(true)
|
|
106
|
+
shared.reactHost.removeReactInstanceEventListener(this)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
shared.reactHost.start()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
83
112
|
|
|
84
|
-
|
|
113
|
+
fun createView(
|
|
114
|
+
activity: FragmentActivity?,
|
|
115
|
+
moduleName: String,
|
|
116
|
+
reactDelegate: ReactDelegateWrapper? = null,
|
|
117
|
+
launchOptions: Bundle? = null,
|
|
118
|
+
): FrameLayout {
|
|
119
|
+
val reactHost = shared.reactHost
|
|
120
|
+
val resolvedDelegate =
|
|
121
|
+
reactDelegate ?: ReactDelegateWrapper(activity, reactHost, moduleName, launchOptions)
|
|
122
|
+
|
|
123
|
+
val mBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
|
124
|
+
override fun handleOnBackPressed() {
|
|
125
|
+
// invoked for JS stack back navigation
|
|
126
|
+
resolvedDelegate.onBackPressed()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Register back press callback
|
|
131
|
+
activity?.onBackPressedDispatcher?.addCallback(mBackPressedCallback)
|
|
132
|
+
// invoked on the last RN screen exit
|
|
133
|
+
resolvedDelegate.setHardwareBackHandler {
|
|
134
|
+
mBackPressedCallback.isEnabled = false
|
|
135
|
+
activity?.onBackPressedDispatcher?.onBackPressed()
|
|
136
|
+
}
|
|
85
137
|
|
|
86
|
-
|
|
87
|
-
|
|
138
|
+
/**
|
|
139
|
+
* When createView method is called in ReactNativeFragment, a reactDelegate
|
|
140
|
+
* instance is required. In such a case, we use the lifeCycle events of the fragment.
|
|
141
|
+
* When createView method is called elsewhere, then reactDelegate is not required.
|
|
142
|
+
* In such a case, we set the lifeCycle observer.
|
|
143
|
+
*/
|
|
144
|
+
if (reactDelegate == null) {
|
|
145
|
+
activity?.lifecycle?.addObserver(getLifeCycleObserver(resolvedDelegate))
|
|
88
146
|
}
|
|
89
147
|
|
|
90
|
-
|
|
148
|
+
resolvedDelegate.loadApp()
|
|
149
|
+
return resolvedDelegate.reactRootView!!
|
|
91
150
|
}
|
|
92
151
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
152
|
+
private fun getLifeCycleObserver(reactDelegate: ReactDelegateWrapper): DefaultLifecycleObserver {
|
|
153
|
+
return object : DefaultLifecycleObserver {
|
|
154
|
+
override fun onResume(owner: LifecycleOwner) {
|
|
155
|
+
reactDelegate.onReactHostResume()
|
|
156
|
+
}
|
|
97
157
|
|
|
98
|
-
|
|
99
|
-
|
|
158
|
+
override fun onPause(owner: LifecycleOwner) {
|
|
159
|
+
reactDelegate.onHostPause()
|
|
160
|
+
}
|
|
100
161
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
override fun onReactContextInitialized(reactContext: ReactContext) {
|
|
106
|
-
callback(true)
|
|
107
|
-
reactInstanceManager.removeReactInstanceEventListener(this)
|
|
162
|
+
override fun onDestroy(owner: LifecycleOwner) {
|
|
163
|
+
reactDelegate.onHostDestroy()
|
|
164
|
+
owner.lifecycle.removeObserver(this) // Cleanup to avoid leaks
|
|
165
|
+
}
|
|
108
166
|
}
|
|
109
|
-
})
|
|
110
|
-
reactInstanceManager?.createReactContextInBackground()
|
|
111
167
|
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
fun createView(
|
|
115
|
-
context: Context,
|
|
116
|
-
activity: FragmentActivity?,
|
|
117
|
-
moduleName: String,
|
|
118
|
-
reactDelegate: ReactDelegateWrapper? = null,
|
|
119
|
-
launchOptions: Bundle? = null,
|
|
120
|
-
): FrameLayout {
|
|
121
|
-
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
122
|
-
val reactHost = getDefaultReactHost(
|
|
123
|
-
context,
|
|
124
|
-
shared.reactNativeHost
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
val resolvedDelegate = reactDelegate ?: ReactDelegateWrapper(activity, reactHost, moduleName, launchOptions)
|
|
128
|
-
|
|
129
|
-
val mBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
|
130
|
-
override fun handleOnBackPressed() {
|
|
131
|
-
// invoked for JS stack back navigation
|
|
132
|
-
resolvedDelegate.onBackPressed()
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
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
|
-
}
|
|
153
|
-
|
|
154
|
-
resolvedDelegate.loadApp()
|
|
155
|
-
return resolvedDelegate.reactRootView!!
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
val instanceManager: ReactInstanceManager? = shared.reactNativeHost?.reactInstanceManager
|
|
159
|
-
val reactView = ReactRootView(context)
|
|
160
|
-
reactView.startReactApplication(
|
|
161
|
-
instanceManager,
|
|
162
|
-
moduleName,
|
|
163
|
-
launchOptions,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
return reactView
|
|
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
|
-
}
|
|
185
168
|
}
|
|
186
|
-
|
package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfieldPackage.kt
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
package com.callstack.reactnativebrownfield
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
|
-
import java.util.Collections
|
|
5
|
-
|
|
6
4
|
import com.facebook.react.ReactPackage
|
|
7
5
|
import com.facebook.react.bridge.NativeModule
|
|
8
6
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
-
import com.facebook.react.uimanager.ViewManager
|
|
10
7
|
import com.facebook.react.uimanager.ReactShadowNode
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
import java.util.Collections
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class ReactNativeBrownfieldPackage : ReactPackage {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> {
|
|
14
|
+
return Collections.emptyList()
|
|
15
|
+
}
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
|
|
18
|
+
val modules = ArrayList<NativeModule>()
|
|
19
|
+
modules.add(ReactNativeBrownfieldModule(reactContext))
|
|
20
|
+
return modules
|
|
21
|
+
}
|
|
23
22
|
}
|
|
@@ -1,164 +1,140 @@
|
|
|
1
|
-
package com.callstack.reactnativebrownfield
|
|
1
|
+
package com.callstack.reactnativebrownfield
|
|
2
2
|
|
|
3
|
-
import android.annotation.TargetApi
|
|
4
|
-
import android.os.Build
|
|
5
3
|
import android.os.Bundle
|
|
6
4
|
import android.util.Log
|
|
7
|
-
import android.view.KeyEvent
|
|
8
5
|
import android.view.LayoutInflater
|
|
9
6
|
import android.view.View
|
|
10
7
|
import android.view.ViewGroup
|
|
11
|
-
import com.
|
|
8
|
+
import com.callstack.reactnativebrownfield.constants.ReactNativeFragmentArgNames
|
|
12
9
|
import com.facebook.react.ReactFragment
|
|
13
10
|
import com.facebook.react.ReactHost
|
|
14
|
-
import com.facebook.react.ReactNativeHost
|
|
15
11
|
import com.facebook.react.bridge.Callback
|
|
16
12
|
import com.facebook.react.bridge.WritableMap
|
|
17
|
-
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
18
|
-
import com.facebook.react.devsupport.DoubleTapReloadRecognizer
|
|
19
13
|
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
20
14
|
import com.facebook.react.modules.core.PermissionListener
|
|
21
15
|
|
|
22
16
|
class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
17
|
+
private lateinit var permissionsCallback: Callback
|
|
18
|
+
private var permissionListener: PermissionListener? = null
|
|
19
|
+
private lateinit var moduleName: String
|
|
20
|
+
|
|
21
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
22
|
+
/**
|
|
23
|
+
* ReactFragment.onCreate will throw an exception if we do not provide arg_component_name as arguments.
|
|
24
|
+
* We silently catch this exception. The reason is we want to invoke the super<Fragment>.onCreate in
|
|
25
|
+
* ReactFragment. Then initialise the mReactDelegate with ReactDelegateWrapper instead of ReactDelegate.
|
|
26
|
+
*
|
|
27
|
+
* So we purposely force ReactFragment.onCreate to throw an exception, so that we can provide our own
|
|
28
|
+
* implementation for mReactDelegate: ReactDelegateWrapper
|
|
29
|
+
*/
|
|
30
|
+
try {
|
|
31
|
+
super.onCreate(savedInstanceState)
|
|
32
|
+
} catch (e: IllegalStateException) {
|
|
33
|
+
Log.w(
|
|
34
|
+
"ReactNativeFragment",
|
|
35
|
+
"ReactFragment threw due to missing arg_component_name: ${e.message} - This is an expected behaviour."
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
moduleName = arguments?.getString(ReactNativeFragmentArgNames.ARG_MODULE_NAME)!!
|
|
40
|
+
this.reactDelegate =
|
|
41
|
+
ReactDelegateWrapper(
|
|
42
|
+
activity,
|
|
43
|
+
this.reactHost,
|
|
44
|
+
moduleName,
|
|
45
|
+
arguments?.getBundle(ReactNativeFragmentArgNames.ARG_LAUNCH_OPTIONS)
|
|
46
|
+
)
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
override fun onCreateView(
|
|
50
|
+
inflater: LayoutInflater,
|
|
51
|
+
container: ViewGroup?,
|
|
52
|
+
savedInstanceState: Bundle?
|
|
53
|
+
): View {
|
|
54
|
+
return ReactNativeBrownfield.shared.createView(
|
|
55
|
+
activity,
|
|
56
|
+
moduleName,
|
|
57
|
+
this.reactDelegate as ReactDelegateWrapper
|
|
58
|
+
)
|
|
47
59
|
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
}
|
|
61
|
+
override val reactHost: ReactHost?
|
|
62
|
+
get() = ReactNativeBrownfield.shared.reactHost
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
override fun onResume() {
|
|
65
|
+
try {
|
|
66
|
+
super.onResume()
|
|
67
|
+
} catch (_: ClassCastException) {
|
|
68
|
+
(this.reactDelegate as ReactDelegateWrapper).onReactHostResume()
|
|
69
|
+
}
|
|
66
70
|
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
override fun getReactNativeHost(): ReactNativeHost? {
|
|
70
|
-
return ReactNativeBrownfield.shared.reactNativeHost
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
override fun onRequestPermissionsResult(
|
|
74
|
-
requestCode: Int,
|
|
75
|
-
permissions: Array<String>,
|
|
76
|
-
grantResults: IntArray
|
|
77
|
-
) {
|
|
78
|
-
permissionsCallback = Callback {
|
|
79
|
-
if (permissionListener != null) {
|
|
80
|
-
permissionListener?.onRequestPermissionsResult(
|
|
81
|
-
requestCode,
|
|
82
|
-
permissions,
|
|
83
|
-
grantResults
|
|
84
|
-
)
|
|
85
71
|
|
|
86
|
-
|
|
87
|
-
|
|
72
|
+
override fun onRequestPermissionsResult(
|
|
73
|
+
requestCode: Int,
|
|
74
|
+
permissions: Array<String>,
|
|
75
|
+
grantResults: IntArray
|
|
76
|
+
) {
|
|
77
|
+
permissionsCallback = Callback {
|
|
78
|
+
if (permissionListener != null) {
|
|
79
|
+
permissionListener?.onRequestPermissionsResult(
|
|
80
|
+
requestCode,
|
|
81
|
+
permissions,
|
|
82
|
+
grantResults
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
permissionListener = null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
88
|
}
|
|
89
|
-
}
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
@TargetApi(Build.VERSION_CODES.M)
|
|
96
|
-
override fun checkSelfPermission(permission: String): Int {
|
|
97
|
-
return requireActivity().checkSelfPermission(permission)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
override fun requestPermissions(
|
|
101
|
-
permissions: Array<String>,
|
|
102
|
-
requestCode: Int,
|
|
103
|
-
listener: PermissionListener?
|
|
104
|
-
) {
|
|
105
|
-
permissionListener = listener
|
|
106
|
-
this.requestPermissions(permissions, requestCode)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
|
110
|
-
var handled = false
|
|
111
|
-
if (ReactNativeBrownfield.shared.reactNativeHost.useDeveloperSupport && ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
|
|
112
|
-
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
|
113
|
-
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager.showDevOptionsDialog()
|
|
114
|
-
handled = true
|
|
115
|
-
}
|
|
116
|
-
val didDoubleTapR = activity?.currentFocus?.let {
|
|
117
|
-
Assertions.assertNotNull(doubleTapReloadRecognizer)
|
|
118
|
-
.didDoubleTapR(keyCode, it)
|
|
119
|
-
}
|
|
120
|
-
if (didDoubleTapR == true) {
|
|
121
|
-
reactDelegate.reload()
|
|
122
|
-
handled = true
|
|
123
|
-
}
|
|
90
|
+
override fun checkPermission(permission: String, pid: Int, uid: Int): Int {
|
|
91
|
+
return requireActivity().checkPermission(permission, pid, uid)
|
|
124
92
|
}
|
|
125
|
-
return handled
|
|
126
|
-
}
|
|
127
93
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@JvmStatic
|
|
132
|
-
@JvmOverloads
|
|
133
|
-
fun createReactNativeFragment(
|
|
134
|
-
moduleName: String,
|
|
135
|
-
initialProps: Bundle? = null
|
|
136
|
-
): ReactNativeFragment {
|
|
137
|
-
val fragment = ReactNativeFragment()
|
|
138
|
-
val args = Bundle()
|
|
139
|
-
args.putString(ARG_MODULE_NAME, moduleName)
|
|
140
|
-
if (initialProps != null) {
|
|
141
|
-
args.putBundle(ARG_LAUNCH_OPTIONS, initialProps)
|
|
142
|
-
}
|
|
143
|
-
fragment.arguments = args
|
|
144
|
-
return fragment
|
|
94
|
+
override fun checkSelfPermission(permission: String): Int {
|
|
95
|
+
return requireActivity().checkSelfPermission(permission)
|
|
145
96
|
}
|
|
146
97
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
)
|
|
152
|
-
|
|
98
|
+
override fun requestPermissions(
|
|
99
|
+
permissions: Array<String>,
|
|
100
|
+
requestCode: Int,
|
|
101
|
+
listener: PermissionListener?
|
|
102
|
+
) {
|
|
103
|
+
permissionListener = listener
|
|
104
|
+
this.requestPermissions(permissions, requestCode)
|
|
153
105
|
}
|
|
154
106
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
107
|
+
companion object {
|
|
108
|
+
@JvmStatic
|
|
109
|
+
@JvmOverloads
|
|
110
|
+
fun createReactNativeFragment(
|
|
111
|
+
moduleName: String,
|
|
112
|
+
initialProps: Bundle? = null
|
|
113
|
+
): ReactNativeFragment {
|
|
114
|
+
val fragment = ReactNativeFragment()
|
|
115
|
+
val args = Bundle()
|
|
116
|
+
args.putString(ReactNativeFragmentArgNames.ARG_MODULE_NAME, moduleName)
|
|
117
|
+
if (initialProps != null) {
|
|
118
|
+
args.putBundle(ReactNativeFragmentArgNames.ARG_LAUNCH_OPTIONS, initialProps)
|
|
119
|
+
}
|
|
120
|
+
fragment.arguments = args
|
|
121
|
+
return fragment
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@JvmStatic
|
|
125
|
+
fun createReactNativeFragment(
|
|
126
|
+
moduleName: String,
|
|
127
|
+
initialProps: HashMap<String, *>
|
|
128
|
+
): ReactNativeFragment {
|
|
129
|
+
return createReactNativeFragment(moduleName, PropsBundle.fromHashMap(initialProps))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@JvmStatic
|
|
133
|
+
fun createReactNativeFragment(
|
|
134
|
+
moduleName: String,
|
|
135
|
+
initialProps: WritableMap
|
|
136
|
+
): ReactNativeFragment {
|
|
137
|
+
return createReactNativeFragment(moduleName, initialProps.toHashMap())
|
|
138
|
+
}
|
|
161
139
|
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
140
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield.constants
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactFragment
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convenience export of arguments that can be used
|
|
7
|
+
*/
|
|
8
|
+
class ReactNativeFragmentArgNames private constructor() :
|
|
9
|
+
ReactFragment() // subclass to gain access to protected constants
|
|
10
|
+
{
|
|
11
|
+
companion object {
|
|
12
|
+
/**
|
|
13
|
+
* The module name to be loaded
|
|
14
|
+
*/
|
|
15
|
+
const val ARG_MODULE_NAME = "arg_module_name"
|
|
16
|
+
|
|
17
|
+
// re-export constants from ReactFragment to enable access
|
|
18
|
+
const val ARG_LAUNCH_OPTIONS: String = "arg_launch_options"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -4,34 +4,34 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
4
4
|
import com.facebook.react.bridge.ReactMethod
|
|
5
5
|
|
|
6
6
|
class ReactNativeBrownfieldModule(reactContext: ReactApplicationContext) :
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
NativeReactNativeBrownfieldModuleSpec(reactContext) {
|
|
8
|
+
companion object {
|
|
9
|
+
var shouldPopToNative: Boolean = false
|
|
10
|
+
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
@ReactMethod
|
|
13
|
+
override fun popToNative(animated: Boolean) {
|
|
14
|
+
shouldPopToNative = true
|
|
15
|
+
onBackPressed()
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
@ReactMethod
|
|
19
|
+
override fun setPopGestureRecognizerEnabled(enabled: Boolean) {
|
|
20
|
+
shouldPopToNative = enabled
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
@ReactMethod
|
|
24
|
+
override fun setHardwareBackButtonEnabled(enabled: Boolean) {
|
|
25
|
+
shouldPopToNative = enabled
|
|
26
|
+
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
private fun onBackPressed() {
|
|
29
|
+
reactApplicationContext.currentActivity?.runOnUiThread {
|
|
30
|
+
reactApplicationContext.currentActivity?.onBackPressed()
|
|
31
|
+
}
|
|
31
32
|
}
|
|
32
|
-
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
override fun getName(): String {
|
|
35
|
+
return "ReactNativeBrownfield"
|
|
36
|
+
}
|
|
37
37
|
}
|
|
@@ -88,7 +88,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate {
|
|
|
88
88
|
initialProps: [AnyHashable: Any]?,
|
|
89
89
|
launchOptions: [AnyHashable: Any]? = nil
|
|
90
90
|
) -> UIView? {
|
|
91
|
-
|
|
91
|
+
rootViewFactory?.view(
|
|
92
92
|
withModuleName: moduleName,
|
|
93
93
|
initialProperties: initialProps,
|
|
94
94
|
launchOptions: launchOptions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@callstack/react-native-brownfield",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Michal Chudziak <mike.chudziak@callstack.com>",
|
|
6
6
|
"contributors": [
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"resolutions": {
|
|
63
|
-
"@types/react": "19.
|
|
63
|
+
"@types/react": "19.1.1"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"react": "*",
|
|
@@ -70,25 +70,25 @@
|
|
|
70
70
|
"@babel/core": "^7.25.2",
|
|
71
71
|
"@babel/preset-env": "^7.25.3",
|
|
72
72
|
"@babel/runtime": "^7.25.0",
|
|
73
|
-
"@react-native/babel-preset": "0.
|
|
74
|
-
"@react-native/eslint-config": "0.
|
|
75
|
-
"@react-native/typescript-config": "0.
|
|
73
|
+
"@react-native/babel-preset": "0.82.1",
|
|
74
|
+
"@react-native/eslint-config": "0.82.1",
|
|
75
|
+
"@react-native/typescript-config": "0.82.1",
|
|
76
76
|
"@release-it/conventional-changelog": "^5.0.0",
|
|
77
77
|
"@types/jest": "^29.5.13",
|
|
78
|
-
"@types/react": "^19.
|
|
79
|
-
"@types/react-test-renderer": "^19.
|
|
78
|
+
"@types/react": "^19.1.1",
|
|
79
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
80
80
|
"babel-plugin-module-resolver": "5.0.0",
|
|
81
81
|
"eslint": "^8.19.0",
|
|
82
82
|
"eslint-config-prettier": "^9.1.0",
|
|
83
83
|
"eslint-plugin-prettier": "^5.1.3",
|
|
84
84
|
"jest": "^29.6.3",
|
|
85
85
|
"prettier": "^3.5.3",
|
|
86
|
-
"react": "19.
|
|
87
|
-
"react-native": "0.
|
|
86
|
+
"react": "19.1.1",
|
|
87
|
+
"react-native": "0.82.1",
|
|
88
88
|
"react-native-builder-bob": "^0.37.0",
|
|
89
|
-
"react-test-renderer": "19.
|
|
89
|
+
"react-test-renderer": "19.1.1",
|
|
90
90
|
"release-it": "^18.1.2",
|
|
91
|
-
"typescript": "5.
|
|
91
|
+
"typescript": "5.8.3"
|
|
92
92
|
},
|
|
93
93
|
"release-it": {
|
|
94
94
|
"git": {
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
]
|
|
173
173
|
},
|
|
174
174
|
"engines": {
|
|
175
|
-
"node": ">=
|
|
175
|
+
"node": ">=20"
|
|
176
176
|
},
|
|
177
177
|
"packageManager": "yarn@3.6.4"
|
|
178
178
|
}
|