@granite-js/screen 1.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/GraniteScreen.podspec +25 -0
  3. package/LICENSE +202 -0
  4. package/android/CMakeLists.txt +62 -0
  5. package/android/build.gradle +63 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/BundleEvaluator.cpp +27 -0
  8. package/android/src/main/cpp/BundleEvaluator.h +17 -0
  9. package/android/src/main/cpp/onLoad.cpp +6 -0
  10. package/android/src/main/kotlin/run/granite/BundleEvaluator.kt +50 -0
  11. package/android/src/main/kotlin/run/granite/BundleLoader.kt +40 -0
  12. package/android/src/main/kotlin/run/granite/DefaultBundleLoader.kt +20 -0
  13. package/android/src/main/kotlin/run/granite/DefaultErrorView.kt +58 -0
  14. package/android/src/main/kotlin/run/granite/DefaultLoadingView.kt +51 -0
  15. package/android/src/main/kotlin/run/granite/GraniteReactDelegate.kt +76 -0
  16. package/android/src/main/kotlin/run/granite/GraniteReactDelegateImpl.kt +448 -0
  17. package/android/src/main/kotlin/run/granite/GraniteReactHost.kt +113 -0
  18. package/android/src/main/kotlin/run/granite/ReactHostFactory.kt +106 -0
  19. package/gradle-plugin/LICENSE +201 -0
  20. package/gradle-plugin/README.md +578 -0
  21. package/gradle-plugin/build.gradle.kts +97 -0
  22. package/gradle-plugin/gradle/libs.versions.toml +17 -0
  23. package/gradle-plugin/gradle/wrapper/gradle-wrapper.jar +0 -0
  24. package/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +7 -0
  25. package/gradle-plugin/gradle.properties +12 -0
  26. package/gradle-plugin/gradlew +248 -0
  27. package/gradle-plugin/gradlew.bat +93 -0
  28. package/gradle-plugin/settings.gradle.kts +1 -0
  29. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteExtension.kt +225 -0
  30. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GranitePlugin.kt +784 -0
  31. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootExtension.kt +107 -0
  32. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootProjectPlugin.kt +290 -0
  33. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/BuildConfigConfigurator.kt +69 -0
  34. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyConfigurator.kt +232 -0
  35. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyCoordinates.kt +29 -0
  36. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DevServerResourceConfigurator.kt +101 -0
  37. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/JniPackagingConfigurator.kt +160 -0
  38. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/NdkConfigurator.kt +135 -0
  39. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/RepositoryConfigurator.kt +148 -0
  40. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/ResourceConfigurator.kt +56 -0
  41. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CMakeGenerator.kt +105 -0
  42. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CppAutolinkingGenerator.kt +152 -0
  43. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/EntryPointGenerator.kt +100 -0
  44. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AndroidDependencyConfig.kt +23 -0
  45. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AutolinkingConfig.kt +89 -0
  46. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/CMakeEntry.kt +47 -0
  47. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/NativeModule.kt +177 -0
  48. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AssetPackagingTask.kt +194 -0
  49. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AutolinkingTask.kt +431 -0
  50. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/BundleTask.kt +275 -0
  51. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenArtifactsTask.kt +218 -0
  52. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenSchemaTask.kt +186 -0
  53. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/AutolinkingParser.kt +128 -0
  54. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ConflictDetector.kt +121 -0
  55. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/JdkValidator.kt +73 -0
  56. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/NodeExecutableFinder.kt +43 -0
  57. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ReactNativeVersionReader.kt +329 -0
  58. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/TaskDependencyValidator.kt +198 -0
  59. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteExtensionTest.kt +191 -0
  60. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GranitePluginTest.kt +156 -0
  61. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteRootProjectPluginTest.kt +87 -0
  62. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/BuildConfigConfiguratorTest.kt +115 -0
  63. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DependencyConfiguratorTest.kt +338 -0
  64. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DevServerResourceConfiguratorTest.kt +205 -0
  65. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/ResourceConfiguratorTest.kt +131 -0
  66. package/gradle-plugin/src/test/kotlin/run/granite/gradle/fixtures/NativeModuleFixtures.kt +67 -0
  67. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CMakeGeneratorTest.kt +71 -0
  68. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CppAutolinkingGeneratorTest.kt +344 -0
  69. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/EntryPointGeneratorTest.kt +40 -0
  70. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/AutolinkingConfigTest.kt +350 -0
  71. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/CMakeEntryTest.kt +200 -0
  72. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/NativeModuleTest.kt +562 -0
  73. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AssetPackagingTaskTest.kt +318 -0
  74. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AutolinkingTaskTest.kt +89 -0
  75. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/BundleTaskTest.kt +68 -0
  76. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/CodegenTasksTest.kt +410 -0
  77. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/AutolinkingParserTest.kt +335 -0
  78. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ConflictDetectorTest.kt +75 -0
  79. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/JdkValidatorTest.kt +88 -0
  80. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ReactNativeVersionReaderTest.kt +585 -0
  81. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskDependencyValidatorTest.kt +123 -0
  82. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskTestUtils.kt +88 -0
  83. package/gradle-plugin/src/test/resources/fixtures/sample-rn-config.json +45 -0
  84. package/ios/BundleLoader/BundleEvaluator.h +16 -0
  85. package/ios/BundleLoader/BundleEvaluator.mm +76 -0
  86. package/ios/BundleLoader/BundleLoadable.swift +91 -0
  87. package/ios/GraniteBundleLoaderTypes.swift +7 -0
  88. package/ios/GraniteScreen.h +12 -0
  89. package/ios/ReactNativeHosting/DefaultViews.swift +138 -0
  90. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.h +24 -0
  91. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.mm +22 -0
  92. package/ios/ReactNativeHosting/GraniteHostingHelper.swift +103 -0
  93. package/ios/ReactNativeHosting/GraniteNativeFactory.swift +35 -0
  94. package/ios/ReactNativeHosting/GraniteNativeFactoryDelegateImpl.swift +30 -0
  95. package/ios/ReactNativeHosting/GraniteNativeFactoryImpl.swift +24 -0
  96. package/ios/ReactNativeHosting/GraniteReactHost.swift +39 -0
  97. package/ios/ReactNativeHosting/GraniteScreen-Bridging-Header.h +12 -0
  98. package/package.json +59 -0
  99. package/react-native.config.js +8 -0
@@ -0,0 +1,76 @@
1
+ package run.granite
2
+
3
+ import android.os.Bundle
4
+ import android.view.View
5
+ import androidx.appcompat.app.AppCompatActivity
6
+ import com.facebook.react.ReactHost
7
+ import com.facebook.react.ReactPackage
8
+ import com.facebook.react.runtime.ReactSurfaceView
9
+
10
+ /**
11
+ * Delegate interface for Granite functionality. This allows integration with existing BaseActivity
12
+ * implementations.
13
+ */
14
+ interface GraniteReactDelegate {
15
+ // Lifecycle methods
16
+ fun onCreate(
17
+ activity: AppCompatActivity,
18
+ savedInstanceState: Bundle?,
19
+ initialProps: Bundle,
20
+ )
21
+
22
+ fun onResume(activity: AppCompatActivity)
23
+
24
+ fun onPause(activity: AppCompatActivity)
25
+
26
+ fun onDestroy(activity: AppCompatActivity)
27
+
28
+ // Configuration methods - these should be provided by the activity
29
+ fun setReactPackagesProvider(provider: () -> List<ReactPackage>)
30
+
31
+ fun setBundleLoaderProvider(provider: () -> BundleLoader)
32
+
33
+ // View creation methods - optional customization
34
+ fun setLoadingViewProvider(provider: (AppCompatActivity) -> View)
35
+
36
+ fun setErrorViewProvider(provider: (AppCompatActivity, Throwable) -> View)
37
+
38
+ fun setReactContainerProvider(provider: (AppCompatActivity) -> android.view.ViewGroup?)
39
+
40
+ // Consumer methods - for advanced view control
41
+
42
+ /**
43
+ * Set a consumer that will be called when loading starts.
44
+ * If not set, default loading view behavior is used.
45
+ */
46
+ fun setLoadingViewConsumer(consumer: () -> Unit)
47
+
48
+ /**
49
+ * Set a consumer that will receive the ReactSurface view when ready.
50
+ * Activity is responsible for adding this view to its layout.
51
+ * If not set, delegate will call setContentView() directly (fallback).
52
+ */
53
+ fun setSurfaceViewConsumer(consumer: (ReactSurfaceView) -> Unit)
54
+
55
+ /**
56
+ * Set a consumer that will be called when bundle loading fails.
57
+ * If not set, default error view behavior is used.
58
+ */
59
+ fun setErrorViewConsumer(consumer: (Throwable) -> Unit)
60
+
61
+ /**
62
+ * Start the ReactSurface. Call this after setting up the surface view in your layout.
63
+ * This triggers surface.start() and applies pending lifecycle states.
64
+ */
65
+ fun startReactSurface()
66
+
67
+ // Access methods
68
+ fun getReactHost(): ReactHost?
69
+
70
+ fun getBundleLoader(): BundleLoader?
71
+
72
+ fun isReady(): Boolean
73
+
74
+ // Surface management
75
+ fun getContentView(): View?
76
+ }
@@ -0,0 +1,448 @@
1
+ package run.granite
2
+
3
+ import android.os.Bundle
4
+ import android.util.Log
5
+ import android.view.View
6
+ import android.widget.FrameLayout
7
+ import androidx.appcompat.app.AppCompatActivity
8
+ import androidx.core.view.ViewCompat
9
+ import androidx.core.view.WindowInsetsCompat
10
+ import androidx.lifecycle.lifecycleScope
11
+ import com.facebook.react.ReactHost
12
+ import com.facebook.react.ReactInstanceEventListener
13
+ import com.facebook.react.ReactPackage
14
+ import com.facebook.react.bridge.ReactContext
15
+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
16
+ import com.facebook.react.fabric.ComponentFactory
17
+ import com.facebook.react.interfaces.fabric.ReactSurface
18
+ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
19
+ import com.facebook.react.runtime.ReactSurfaceView
20
+ import kotlinx.coroutines.launch
21
+ import java.lang.ref.WeakReference
22
+
23
+ /** Default implementation of GraniteReactDelegate. Manages ReactHost, ReactSurface */
24
+ @OptIn(UnstableReactNativeAPI::class)
25
+ class GraniteReactDelegateImpl : GraniteReactDelegate {
26
+ // Lifecycle state enum to track pending state when ReactHost is null
27
+ private enum class LifecycleState {
28
+ BEFORE_CREATE,
29
+ CREATED,
30
+ RESUMED,
31
+ PAUSED,
32
+ DESTROYED,
33
+ }
34
+
35
+ private var reactHost: ReactHost? = null
36
+ private var reactSurface: ReactSurface? = null
37
+ private var currentBundleSource: BundleSource? = null
38
+ private var currentBundleLoader: BundleLoader? = null
39
+ private var contentView: View? = null
40
+ private var componentFactory: ComponentFactory? = null
41
+ private var reactInstanceEventListener: ReactInstanceEventListener? = null
42
+
43
+ // Track pending lifecycle state for when ReactHost is created asynchronously
44
+ private var pendingLifecycleState: LifecycleState = LifecycleState.BEFORE_CREATE
45
+
46
+ // Use WeakReference to prevent Activity memory leak
47
+ private var pendingActivityRef: WeakReference<AppCompatActivity>? = null
48
+ private val pendingActivity: AppCompatActivity?
49
+ get() = pendingActivityRef?.get()
50
+
51
+ // Provider functions - to be set by the activity
52
+ private var reactPackagesProvider: (() -> List<ReactPackage>)? = null
53
+ private var bundleLoaderProvider: (() -> BundleLoader)? = null
54
+ private var loadingViewProvider: ((AppCompatActivity) -> View)? = null
55
+ private var errorViewProvider: ((AppCompatActivity, Throwable) -> View)? = null
56
+ private var reactContainerProvider: ((AppCompatActivity) -> android.view.ViewGroup?)? = null
57
+
58
+ // Consumer functions - for advanced view control
59
+ private var loadingViewConsumer: (() -> Unit)? = null
60
+ private var surfaceViewConsumer: ((ReactSurfaceView) -> Unit)? = null
61
+ private var errorViewConsumer: ((Throwable) -> Unit)? = null
62
+
63
+ override fun onCreate(
64
+ activity: AppCompatActivity,
65
+ savedInstanceState: Bundle?,
66
+ initialProps: Bundle,
67
+ ) {
68
+ // Track lifecycle state
69
+ pendingLifecycleState = LifecycleState.CREATED
70
+ pendingActivityRef = WeakReference(activity)
71
+
72
+ // Get packages
73
+ val packages = reactPackagesProvider?.invoke() ?: emptyList()
74
+
75
+ Log.d(TAG, "Using ${packages.size} React packages")
76
+ packages.forEach { pkg ->
77
+ Log.d(TAG, "- ${pkg.javaClass.simpleName}")
78
+ }
79
+
80
+ if (BuildConfig.DEBUG) {
81
+ Log.d(TAG, "Running in debug mode")
82
+ }
83
+
84
+ // Require BundleLoader
85
+ val bundleLoader =
86
+ bundleLoaderProvider?.invoke()
87
+ ?: throw IllegalStateException("BundleLoader provider not set")
88
+ currentBundleLoader = bundleLoader
89
+
90
+ // Show loading view and start bundle loading
91
+ // Use consumer if set, otherwise use provider/default
92
+ loadingViewConsumer?.invoke()
93
+ ?: run {
94
+ val loadingView =
95
+ loadingViewProvider?.invoke(activity) ?: DefaultLoadingView(activity)
96
+ contentView = loadingView
97
+ activity.setContentView(loadingView)
98
+ }
99
+
100
+ loadBundleWithLoader(activity, bundleLoader, initialProps)
101
+ }
102
+
103
+ override fun onResume(activity: AppCompatActivity) {
104
+ // Track pending lifecycle state
105
+ pendingLifecycleState = LifecycleState.RESUMED
106
+ pendingActivityRef = WeakReference(activity)
107
+
108
+ // Call onHostResume if ReactHost is ready, otherwise it will be called in setupReactHost
109
+ val backBtnHandler = activity as? DefaultHardwareBackBtnHandler
110
+ ?: throw IllegalStateException(
111
+ "Activity ${activity.javaClass.simpleName} must implement DefaultHardwareBackBtnHandler",
112
+ )
113
+ reactHost?.onHostResume(activity, backBtnHandler)
114
+ ?: Log.w(
115
+ TAG,
116
+ "ReactHost not ready yet, will call onHostResume after creation",
117
+ )
118
+ }
119
+
120
+ override fun onPause(activity: AppCompatActivity) {
121
+ // Track pending lifecycle state
122
+ pendingLifecycleState = LifecycleState.PAUSED
123
+ pendingActivityRef = WeakReference(activity)
124
+
125
+ // Call onHostPause only if ReactHost exists
126
+ reactHost?.onHostPause(activity)
127
+ ?: Log.w(TAG, "ReactHost not ready, skipping onHostPause")
128
+ }
129
+
130
+ override fun onDestroy(activity: AppCompatActivity) {
131
+ // Track pending lifecycle state
132
+ pendingLifecycleState = LifecycleState.DESTROYED
133
+ pendingActivityRef = null
134
+
135
+ // Clean up ReactSurface
136
+ // stop() is async (TaskInterface<Void>), but clear() and detach() can be called concurrently:
137
+ // - clear() removes view children via UiThreadUtil.runOnUiThread (thread-safe)
138
+ // - detach() releases host reference via AtomicReference.set(null) (thread-safe)
139
+ reactSurface?.let { surface ->
140
+ surface.stop()
141
+ surface.clear()
142
+ surface.detach()
143
+ }
144
+ reactSurface = null
145
+
146
+ // Remove ReactInstanceEventListener
147
+ reactInstanceEventListener?.let { listener ->
148
+ reactHost?.removeReactInstanceEventListener(listener)
149
+ }
150
+ reactInstanceEventListener = null
151
+
152
+ // Clean up ReactHost
153
+ // invalidate() internally runs destroy() asynchronously on bgExecutor.
154
+ // ReactHostImpl instance is kept alive by its own internal threading,
155
+ // so nulling the delegate's reactHost field won't interrupt the async destroy.
156
+ reactHost?.let { host ->
157
+ host.onHostDestroy(activity)
158
+ host.invalidate()
159
+ }
160
+ ?: Log.w(TAG, "ReactHost not ready, skipping onHostDestroy")
161
+ reactHost = null
162
+
163
+ // Clean up internal references
164
+ contentView = null
165
+ currentBundleSource = null
166
+ currentBundleLoader = null
167
+ componentFactory = null
168
+
169
+ // Clean up Provider/Consumer lambdas (may capture Activity via closure)
170
+ reactPackagesProvider = null
171
+ bundleLoaderProvider = null
172
+ loadingViewProvider = null
173
+ errorViewProvider = null
174
+ reactContainerProvider = null
175
+ loadingViewConsumer = null
176
+ surfaceViewConsumer = null
177
+ errorViewConsumer = null
178
+ }
179
+
180
+ override fun setReactPackagesProvider(provider: () -> List<ReactPackage>) {
181
+ this.reactPackagesProvider = provider
182
+ }
183
+
184
+ override fun setBundleLoaderProvider(provider: () -> BundleLoader) {
185
+ this.bundleLoaderProvider = provider
186
+ }
187
+
188
+ override fun setLoadingViewProvider(provider: (AppCompatActivity) -> View) {
189
+ this.loadingViewProvider = provider
190
+ }
191
+
192
+ override fun setErrorViewProvider(provider: (AppCompatActivity, Throwable) -> View) {
193
+ this.errorViewProvider = provider
194
+ }
195
+
196
+ override fun setReactContainerProvider(provider: (AppCompatActivity) -> android.view.ViewGroup?) {
197
+ this.reactContainerProvider = provider
198
+ }
199
+
200
+ override fun setLoadingViewConsumer(consumer: () -> Unit) {
201
+ this.loadingViewConsumer = consumer
202
+ }
203
+
204
+ override fun setSurfaceViewConsumer(consumer: (ReactSurfaceView) -> Unit) {
205
+ this.surfaceViewConsumer = consumer
206
+ }
207
+
208
+ override fun setErrorViewConsumer(consumer: (Throwable) -> Unit) {
209
+ this.errorViewConsumer = consumer
210
+ }
211
+
212
+ override fun getReactHost(): ReactHost? = reactHost
213
+
214
+ override fun getBundleLoader(): BundleLoader? = currentBundleLoader
215
+
216
+ override fun isReady(): Boolean = reactHost != null && reactSurface != null
217
+
218
+ override fun getContentView(): View? = contentView
219
+
220
+ private fun getMainComponentName(): String =
221
+ currentBundleSource?.componentName
222
+ ?: throw IllegalStateException(
223
+ "mainComponentName is missing. Use a BundleLoader that provides componentName.",
224
+ )
225
+
226
+ // Default (no-loader) ReactHost creation removed; BundleLoader is mandatory.
227
+
228
+ private fun setupReactHost(
229
+ activity: AppCompatActivity,
230
+ initialProps: Bundle,
231
+ ) {
232
+ reactHost?.let { host ->
233
+ // Get component name using the method
234
+ val componentName = getMainComponentName()
235
+
236
+ Log.d(TAG, "Using component name: $componentName")
237
+
238
+ // Create surface
239
+ val surface = host.createSurface(activity, componentName, initialProps)
240
+ reactSurface = surface
241
+ contentView = surface.view
242
+
243
+ (surface.view as? ReactSurfaceView)?.let { surfaceView ->
244
+ surfaceViewConsumer?.invoke(surfaceView)
245
+ ?: run {
246
+ // Fallback: Use default behavior if consumer not set
247
+ surfaceView.id = View.NO_ID
248
+ activity.setContentView(surfaceView)
249
+ setupWindowInsets(surfaceView)
250
+
251
+ // Start surface immediately if consumer not used
252
+ startReactSurface()
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ private fun setupWindowInsets(surfaceView: View) {
259
+ val insetsType: Int =
260
+ WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
261
+
262
+ val windowInsetsListener = { view: View, windowInsets: WindowInsetsCompat ->
263
+ val insets = windowInsets.getInsets(insetsType)
264
+
265
+ (view.layoutParams as? FrameLayout.LayoutParams)?.apply {
266
+ setMargins(insets.left, insets.top, insets.right, insets.bottom)
267
+ }
268
+
269
+ WindowInsetsCompat.CONSUMED
270
+ }
271
+ ViewCompat.setOnApplyWindowInsetsListener(surfaceView, windowInsetsListener)
272
+ }
273
+
274
+ override fun startReactSurface() {
275
+ reactHost?.let { host ->
276
+ reactSurface?.let { surface ->
277
+ Log.d(TAG, "Starting ReactSurface")
278
+
279
+ // Add ReactInstanceEventListener
280
+ val listener = object : ReactInstanceEventListener {
281
+ override fun onReactContextInitialized(context: ReactContext) {
282
+ Log.d(TAG, "Granite onReactContextInitialized called")
283
+ Log.d(
284
+ TAG,
285
+ "Granite onReactContextInitialized on currentActivity = ${context.currentActivity}",
286
+ )
287
+ // Call the host's onReactContextInitialized method
288
+ pendingActivity?.let { activity ->
289
+ if (activity is GraniteReactHost) {
290
+ Log.d(
291
+ TAG,
292
+ "Granite activity.onReactContextInitialized called",
293
+ )
294
+ activity.onReactContextInitialized(context)
295
+ }
296
+ }
297
+ }
298
+ }
299
+ reactInstanceEventListener = listener
300
+ host.addReactInstanceEventListener(listener)
301
+
302
+ // Start the surface
303
+ Log.d(TAG, "About to call surface.start()")
304
+ Log.d(TAG, "surface = $surface")
305
+ Log.d(
306
+ TAG,
307
+ "If AppRegistry.registerComponent was not called, this will fail!",
308
+ )
309
+ surface.start()
310
+ Log.d(TAG, "surface.start() completed")
311
+
312
+ // Apply pending lifecycle state
313
+ Log.d(
314
+ TAG,
315
+ "Applying pending lifecycle state: $pendingLifecycleState",
316
+ )
317
+ when (pendingLifecycleState) {
318
+ LifecycleState.RESUMED -> {
319
+ // Activity is resumed, call onHostResume
320
+ pendingActivity?.let { act ->
321
+ val handler = act as? DefaultHardwareBackBtnHandler
322
+ ?: throw IllegalStateException(
323
+ "Activity ${act.javaClass.simpleName} must implement DefaultHardwareBackBtnHandler",
324
+ )
325
+ host.onHostResume(act, handler)
326
+ Log.d(
327
+ TAG,
328
+ "Called onHostResume with activity: $act",
329
+ )
330
+ }
331
+ }
332
+ LifecycleState.PAUSED -> {
333
+ // Activity is paused, call onHostPause
334
+ pendingActivity?.let { act ->
335
+ host.onHostPause(act)
336
+ Log.d(
337
+ TAG,
338
+ "Called onHostPause with activity: $act",
339
+ )
340
+ }
341
+ }
342
+ LifecycleState.DESTROYED -> {
343
+ // Activity is being destroyed, clean up immediately
344
+ surface.stop()
345
+ pendingActivity?.let { act -> host.onHostDestroy(act) }
346
+ host.invalidate()
347
+ Log.d(TAG, "Activity destroyed, cleaned up ReactHost")
348
+ }
349
+ else -> {
350
+ Log.d(TAG, "No lifecycle state to apply")
351
+ }
352
+ }
353
+ }
354
+ ?: Log.w(TAG, "ReactSurface is null, cannot start")
355
+ }
356
+ ?: Log.w(TAG, "ReactHost is null, cannot start")
357
+ }
358
+
359
+ private fun loadBundleWithLoader(
360
+ activity: AppCompatActivity,
361
+ bundleLoader: BundleLoader,
362
+ initialProps: Bundle,
363
+ ) {
364
+ activity.lifecycleScope.launch {
365
+ try {
366
+ Log.d(TAG, "loadBundleWithLoader started")
367
+ Log.d(
368
+ TAG,
369
+ "bundleLoader type = ${bundleLoader.javaClass.simpleName}",
370
+ )
371
+
372
+ // Load bundle (suspend, may run on IO dispatcher)
373
+ val bundleSource = bundleLoader.loadBundle()
374
+
375
+ Log.d(TAG, "bundleSource loaded = $bundleSource")
376
+ when (bundleSource) {
377
+ is BundleSource.Production -> {
378
+ Log.d(
379
+ TAG,
380
+ "bundleSource.location = ${bundleSource.location}",
381
+ )
382
+ Log.d(
383
+ TAG,
384
+ "bundleSource.componentName = ${bundleSource.componentName}",
385
+ )
386
+ if (bundleSource.location is ProductionLocation.FileSystemBundle) {
387
+ Log.d(
388
+ TAG,
389
+ "ACTUAL FILE PATH = ${(bundleSource.location as ProductionLocation.FileSystemBundle).filePath}",
390
+ )
391
+ }
392
+ }
393
+ is BundleSource.DevServer -> {
394
+ Log.d(
395
+ TAG,
396
+ "DevServer mode - ${bundleSource.host}:${bundleSource.port}",
397
+ )
398
+ }
399
+ }
400
+
401
+ // Create ReactHost with the loaded bundle (safe off main thread)
402
+ val factoryResult = createReactHostWithBundle(activity, bundleSource)
403
+
404
+ // Assign shared state and setup on UI thread to serialize with onDestroy
405
+ activity.runOnUiThread {
406
+ if (pendingLifecycleState != LifecycleState.DESTROYED) {
407
+ currentBundleSource = bundleSource
408
+ reactHost = factoryResult.reactHost
409
+ componentFactory = factoryResult.componentFactory
410
+ setupReactHost(activity, initialProps)
411
+ } else {
412
+ // Activity already destroyed; clean up the host we just created
413
+ factoryResult.reactHost.invalidate()
414
+ }
415
+ }
416
+ } catch (e: Exception) {
417
+ // Show error view on UI thread
418
+ activity.runOnUiThread {
419
+ if (pendingLifecycleState != LifecycleState.DESTROYED) {
420
+ errorViewConsumer?.invoke(e)
421
+ ?: run {
422
+ // Fallback: Use default behavior if consumer not set
423
+ val errorView =
424
+ errorViewProvider?.invoke(activity, e)
425
+ ?: DefaultErrorView(activity, e)
426
+ contentView = errorView
427
+ activity.setContentView(errorView)
428
+ }
429
+ Log.e(TAG, "Failed to load bundle", e)
430
+ }
431
+ }
432
+ }
433
+ }
434
+ }
435
+
436
+ private fun createReactHostWithBundle(
437
+ activity: AppCompatActivity,
438
+ bundleSource: BundleSource,
439
+ ): ReactHostFactory.Result {
440
+ val packages = reactPackagesProvider?.invoke() ?: emptyList()
441
+ return ReactHostFactory.create(activity.applicationContext, bundleSource, packages)
442
+ }
443
+
444
+ companion object {
445
+ // Note: BuildConfig.DEBUG reflects this library's build variant, not the consuming app's.
446
+ private const val TAG = "GraniteReactDelegate"
447
+ }
448
+ }
@@ -0,0 +1,113 @@
1
+ package run.granite
2
+
3
+ import android.os.Bundle
4
+ import android.view.View
5
+ import androidx.appcompat.app.AppCompatActivity
6
+ import com.facebook.react.ReactHost
7
+ import com.facebook.react.ReactPackage
8
+ import com.facebook.react.bridge.ReactContext
9
+
10
+ /**
11
+ * Host interface for integrating React Native into activities. This interface allows you to "host"
12
+ * React Native components in any Activity, regardless of your existing inheritance hierarchy.
13
+ *
14
+ * The GraniteReactHost pattern provides maximum flexibility for integrating React Native into
15
+ * existing Android applications with their own BaseActivity implementations.
16
+ *
17
+ * Usage example:
18
+ * ```kotlin
19
+ * class MyActivity : CompanyBaseActivity(), GraniteReactHost {
20
+ * override val graniteDelegate = GraniteActivityDelegateImpl()
21
+ *
22
+ * override fun onCreate(savedInstanceState: Bundle?) {
23
+ * super.onCreate(savedInstanceState)
24
+ * setupHost(savedInstanceState) // Automatically manages all lifecycle events
25
+ * }
26
+ *
27
+ * override fun getReactPackages() = PackageList(this).packages
28
+ * override fun getGraniteModules(context: ReactContext) = listOf(MyModule())
29
+ * }
30
+ * ```
31
+ */
32
+ interface GraniteReactHost {
33
+ /** The delegate that handles all GraniteModule logic */
34
+ val graniteDelegate: GraniteReactDelegate
35
+
36
+ /** Initialize the React Native host. Call this in your activity's onCreate() method. */
37
+ fun AppCompatActivity.setupHost(
38
+ savedInstanceState: Bundle?,
39
+ initialProps: Bundle,
40
+ ) {
41
+ // Configure delegate with providers
42
+ graniteDelegate.setReactPackagesProvider { getReactPackages() }
43
+ graniteDelegate.setBundleLoaderProvider { createBundleLoader() }
44
+ graniteDelegate.setLoadingViewProvider { activity -> createLoadingView() }
45
+ graniteDelegate.setErrorViewProvider { activity, error -> createErrorView(error) }
46
+
47
+ // Initialize
48
+ graniteDelegate.onCreate(this, savedInstanceState, initialProps)
49
+ }
50
+
51
+ /** Resume the React Native host. Call this in your activity's onResume() method. */
52
+ fun AppCompatActivity.resumeHost() {
53
+ graniteDelegate.onResume(this)
54
+ }
55
+
56
+ /** Pause the React Native host. Call this in your activity's onPause() method. */
57
+ fun AppCompatActivity.pauseHost() {
58
+ graniteDelegate.onPause(this)
59
+ }
60
+
61
+ /** Destroy the React Native host. Call this in your activity's onDestroy() method. */
62
+ fun AppCompatActivity.destroyHost() {
63
+ graniteDelegate.onDestroy(this)
64
+ }
65
+
66
+ // Abstract methods to be implemented by the activity
67
+
68
+ /**
69
+ * Get React packages including core packages and custom modules. Typically returns
70
+ * PackageList(this).packages
71
+ */
72
+ fun getReactPackages(): List<ReactPackage>
73
+
74
+ /**
75
+ * Provide a BundleLoader implementation. This is now required and must return a valid loader
76
+ * that supplies the component (module) name via BundleSource.
77
+ */
78
+ fun createBundleLoader(): BundleLoader
79
+
80
+ /**
81
+ * Create a custom loading view displayed while bundle is loading. Optional - defaults to
82
+ * DefaultLoadingView.
83
+ */
84
+ fun createLoadingView(): View = DefaultLoadingView(this as AppCompatActivity)
85
+
86
+ /**
87
+ * Create a custom error view displayed when bundle loading fails. Optional - defaults to
88
+ * DefaultErrorView.
89
+ */
90
+ fun createErrorView(error: Throwable): View = DefaultErrorView(this as AppCompatActivity, error)
91
+
92
+ /**
93
+ * Called when ReactContext is initialized and ready. Override this method to perform actions
94
+ * that require ReactContext, such as registering Brick modules.
95
+ *
96
+ * @param context The initialized ReactContext
97
+ */
98
+ fun onReactContextInitialized(context: ReactContext) {
99
+ // Default implementation does nothing
100
+ // Override in your activity to register modules or perform other initialization
101
+ }
102
+
103
+ // Convenience accessors
104
+
105
+ /** Get the current bundle loader if any. */
106
+ fun getBundleLoader(): BundleLoader? = graniteDelegate.getBundleLoader()
107
+
108
+ /** Get the ReactHost for advanced use cases. */
109
+ fun getReactHost(): ReactHost? = graniteDelegate.getReactHost()
110
+
111
+ /** Check if the React Native host is ready (ReactHost and surface initialized). */
112
+ fun isHostReady(): Boolean = graniteDelegate.isReady()
113
+ }