Conversation
…wnfield provider artifact
There was a problem hiding this comment.
Pull request overview
This PR adds a postMessage/onMessage messaging API to @callstack/react-native-brownfield, enabling JSON-based message exchange between the React Native JS layer and native host apps, plus updated docs and demo apps to showcase usage.
Changes:
- Added JS
postMessageandonMessageAPIs backed by a TurboModule event emitter (onBrownfieldMessage). - Implemented native-side message plumbing on iOS and Android (including native-to-JS emission helpers and JS-to-native dispatch listeners).
- Updated API reference docs and reworked demo apps (RNApp, ExpoApp, native consumer demos) to demonstrate the messaging flow.
Reviewed changes
Copilot reviewed 44 out of 47 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Dependency lockfile updates (Expo-related bumps). |
| packages/react-native-brownfield/src/index.ts | Exposes JS postMessage/onMessage API surface. |
| packages/react-native-brownfield/src/NativeReactNativeBrownfieldModule.ts | Adds postMessage + onBrownfieldMessage event emitter to the TurboModule spec. |
| packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.swift | Adds JS→native message notification posting helper. |
| packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.mm | Exports native module method and forwards native→JS notifications into the JS event emitter. |
| packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.h | Declares static native helper for emitting messages to JS. |
| packages/react-native-brownfield/ios/ReactNativeBrownfield.swift | Adds host-side native→JS postMessage and JS→native onMessage subscription. |
| packages/react-native-brownfield/ios/Notification+Brownfield.swift | Defines notification names for message exchange. |
| packages/react-native-brownfield/android/src/oldarch/ReactNativeBrownfieldModule.kt | Old-arch JS→native dispatch + no-op native→JS emitter with warning. |
| packages/react-native-brownfield/android/src/newarch/ReactNativeBrownfieldModule.kt | New-arch JS→native dispatch + native→JS event emission. |
| packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt | Adds native message listeners + dispatch + native→JS entrypoint. |
| docs/docs/index.md | Updates homepage feature blurb related to messaging/state sync. |
| docs/docs/docs/api-reference/react-native-brownfield/swift.mdx | Documents Swift postMessage / onMessage. |
| docs/docs/docs/api-reference/react-native-brownfield/objective-c.mdx | Documents ObjC postMessage / onMessage. |
| docs/docs/docs/api-reference/react-native-brownfield/kotlin.mdx | Documents Kotlin postMessage + message listeners. |
| docs/docs/docs/api-reference/react-native-brownfield/javascript.mdx | Documents JS postMessage + onMessage subscription. |
| docs/docs/docs/api-reference/react-native-brownfield/java.mdx | Documents Java postMessage + message listeners. |
| apps/TesterIntegrated/swift/App.swift | Swift demo UI updated to exercise message exchange. |
| apps/TesterIntegrated/kotlin/app/src/main/java/com/callstack/kotlinexample/MainActivity.kt | Kotlin demo UI updated to exercise message exchange. |
| apps/RNApp/src/HomeScreen.tsx | RN demo updated with chat-like UI around postMessage/onMessage. |
| apps/RNApp/ios/Podfile.lock | Updates iOS demo pods (Brownie/ReactBrownfield versions). |
| apps/ExpoApp/package.json | Bumps Expo dependencies for the Expo demo. |
| apps/ExpoApp/components/ui/icon-symbol.tsx | Adds icon mapping for new tab icon. |
| apps/ExpoApp/components/postMessage/MessageBubble.tsx | Adds message bubble component for Expo demo. |
| apps/ExpoApp/components/postMessage/Message.ts | Adds message model for Expo demo. |
| apps/ExpoApp/app/(tabs)/postMessage.tsx | Adds postMessage demo tab screen for Expo app. |
| apps/ExpoApp/app/(tabs)/index.tsx | Refactors tab index screen layout wrapper. |
| apps/ExpoApp/app/(tabs)/_layout.tsx | Adds a new postMessage tab. |
| apps/AppleApp/prepareXCFrameworks.js | Patches iOS consumer ContentView moduleName/GreetingCard based on app type. |
| apps/AppleApp/package.json | Updates iOS consumer build script configuration name. |
| apps/AndroidApp/app/src/vanilla/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt | Adds app name constant for Android demo. |
| apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/components/PostMessageCard.kt | Adds Compose card demonstrating postMessage in Android demo. |
| apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/components/MaterialCard.kt | Adds shared Compose card wrapper component. |
| apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/components/GreetingCard.kt | Extracts greeting UI into its own component. |
| apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt | Refactors Android demo screen to use extracted components + postMessage card. |
| apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt | Adjusts main module name + app name for Expo Android demo variant. |
| .changeset/fair-zebras-heal.md | Declares a minor version bump for the new API. |
Comments suppressed due to low confidence (3)
packages/react-native-brownfield/android/src/newarch/ReactNativeBrownfieldModule.kt:31
- The New Architecture module stores a static
sharedInstancereference to the module and never clears it. This can keep theReactApplicationContextalive longer than intended and can also leave a stale instance after RN teardown/recreation (e.g., fast refresh / host recreation). Consider using aWeakReferenceand/or clearingsharedInstancein the appropriate lifecycle hook (invalidate/onCatalystInstanceDestroy, depending on the base class).
apps/TesterIntegrated/swift/App.swift:141 - This builds JSON by string interpolation:
{"text":"\(text)"}. Iftextcontains quotes, backslashes, or newlines, the JSON becomes invalid and parsing on the RN side will fail. Consider building the payload viaJSONSerialization/JSONEncoderso strings are properly escaped.
Button("Send") {
let text = draft.isEmpty ? "Hello from Swift! (#\(nextId))" : draft
let json = "{\"text\":\"\(text)\"}"
ReactNativeBrownfield.shared.postMessage(json)
withAnimation(.spring(response: 0.35, dampingFraction: 0.7)) {
docs/docs/index.md:22
- The phrase "message exchange lag" reads like a typo in this context (it describes a feature, but "lag" means delay). If you meant the integration "layer", consider updating the wording to avoid confusion.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| static ReactNativeBrownfieldModule *_sharedInstance = nil; | ||
|
|
||
| - (instancetype)init { | ||
| self = [super init]; | ||
| if (self) { | ||
| _sharedInstance = self; | ||
|
|
||
| [[NSNotificationCenter defaultCenter] addObserver:self | ||
| selector:@selector(handleNativeToJSMessage:) | ||
| name:@"BrownfieldMessageToJSNotification" | ||
| object:nil]; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)dealloc { | ||
| [[NSNotificationCenter defaultCenter] removeObserver:self]; | ||
| } |
There was a problem hiding this comment.
_sharedInstance is a static raw pointer that is never cleared. If the module instance is deallocated (bridge invalidation / reload), _sharedInstance can become a dangling pointer and emitMessageFromNative: may message a deallocated instance (crash). In dealloc, clear _sharedInstance (ideally only if it equals self) and consider guarding access for multi-instance scenarios.
| private const val LOG_TAG = "ReactNativeBrownfieldModule" | ||
|
|
||
| fun emitMessageFromNative(text: String) { | ||
| Log.w( | ||
| LOG_TAG, | ||
| "ReactNativeBrownfieldModule::emitMessageFromNative only supports the New Architecture. This call is ineffective and will not cause any messages to be emitted." | ||
| ) |
There was a problem hiding this comment.
Log.w(...) is used in emitMessageFromNative, but android.util.Log is not imported in this file, which will fail compilation. Add the missing import (or remove logging if not needed).
| postMessage: (data: unknown): void => { | ||
| const serialized = JSON.stringify(data); | ||
| ReactNativeBrownfieldModule.postMessage(serialized); | ||
| }, |
There was a problem hiding this comment.
postMessage uses JSON.stringify(data) without handling failures. JSON.stringify can throw (e.g., cyclic structures / BigInt) and can also return undefined (e.g., undefined, functions), which would then be passed to the native postMessage(message: string) and likely fail at runtime. Consider wrapping serialization in try/catch and either (a) throw a clear JS-side error or (b) normalize undefined to a valid string (e.g., 'null') before calling the native module.
Summary
This PR introduces a
postMessageAPI that resembles the web API.postMessagefunctionality is fully enabled for the New Architecture.postMessageis a no-op, with a warning log.ReactNativeBrownfieldModule(New Architecture) now includesemitMessageFromNativefor native-to-JS communication.addMessageListenerandremoveMessageListeneris introduced.postMessageis implemented to send JSON strings from native to the React Native JS layer.onMessageprovides a mechanism for native code to subscribe to messages sent from JavaScript.ReactNativeBrownfield.postMessagesends JSON-serializable data to native.ReactNativeBrownfield.onMessagesubscribes to native-to-JS messages, returning a subscription object.postMessage-related API documentationpostMessageusageTest plan
CI green + manual tests