react-native-android-bridge-implementation
Implements the Android Kotlin bridge layer for a React-Native MoEngage SDK feature. This is Step 2a of the React-Native feature pipeline — run AFTER plugin-base-feature-implementation has completed. P
SKILL.md.claude/skills/react-native-android-bridge-implementation/SKILL.md
GitHubOverview
Implements the Android Kotlin bridge inside the React-Native SDK repo (React-Native) for a
MoEngage feature whose plugin-base module already exists in ../android-plugin-base.
Prerequisite chain:
plugin-base-feature-implementation— creates/extends the Android plugin-base module ✅react-native-android-bridge-implementation← you are herereact-native-ts-implementation— TypeScript models, spec, Handler, public API
Architecture standard: Follow the Cards module pattern exactly. The Cards module is the canonical reference for all structural decisions.
Example files: Templates are in examples/ adjacent to this SKILL.md. Read each
template before generating the corresponding file.
Example Files Index
examples/
Constants.kt ← <rnPackage>/Constants.kt
BridgeHandler.kt ← <rnBridgeName>Handler.kt (all methods)
EventEmitterImpl.kt ← EventEmitterImpl.kt (nativeToHybrid / event methods only)
PayloadGenerator.kt ← PayloadGenerator.kt (nativeToHybrid / event methods only)
Package.kt ← MoEngage<featureNameCamel>Package.kt
NewArchBridge.kt ← src/newarch/ TurboModule bridge
OldArchBridge.kt ← src/oldarch/ ReactMethod bridge
Phase 0 — Clarify Inputs
0.1 Extract ticket ID
Scan the user's full command for MOEN-\d+ → ticketId.
If not found in the command or parameters, ask before proceeding.
Phase 1 — Parse Inputs & Derive All Identifiers
1.1 Extract from contract_branch
Strip everything up to and including the first / or _MOEN-XXXXX_ prefix:
feature/experience_contracts→contractSuffix=experience_contractsMOEN-44072_jwt_contract→contractSuffix=jwt_contract
1.2 Identifiers table
| Identifier | Example | Rule |
|---|---|---|
ticketId |
MOEN-44072 |
MOEN-\d+ from raw command or parameter |
contractSuffix |
jwt_contract |
branch name after first / or _MOEN-XXXXX_ |
featureName |
jwt |
lowercase slug from feature_description |
featureNameCamel |
Jwt |
PascalCase of featureName |
featureNameUpper |
JWT |
UPPER_SNAKE of featureName |
contractDir |
authentication |
subdirectory found in contracts json/ after checkout |
androidModuleName |
plugin-base-jwt |
plugin-base-<featureName> (or the actual module from plugin-base PR) |
rnSdkDir |
sdk/core |
see rule below |
rnPackage |
com.moengage.react.jwt |
com.moengage.react.<featureName> |
rnBridgeName |
MoEngageJwtBridge |
MoEngage<featureNameCamel>Bridge |
branchName |
feature/MOEN-44072-jwt_contract |
feature/<ticketId>-<contractSuffix> |
1.3 Resolve rnSdkDir
Scan feature_description for a framework keyword and map to the existing SDK module directory:
Keyword in feature_description |
rnSdkDir |
|---|---|
core, analytics, inapps, or messaging |
sdk/core |
cards |
sdk/cards |
geofence |
sdk/geofence |
inbox |
sdk/inbox |
personalize |
sdk/personalize |
| none of the above | ask the user which SDK module to add the feature to |
Examples:
"JWT authentication parity from core"→sdk/core"get clicked cards count"→sdk/cards"start geofence monitoring"→sdk/geofence
Phase 2 — Read Contracts
cd ../mobile-sdk-contracts
git fetch
git stash
git checkout <contract_branch>
- List
json/hybridToNative/to identifycontractDir- If no matching directory → list available dirs and ask the user
- For each
.jsoninjson/hybridToNative/<contractDir>/:- Filename (without
.json) = method name (camelCase) - File content = input payload shape
- Filename (without
- For each
.jsoninjson/nativeToHybrid/<contractDir>/:- File content = event/response payload shape
- Read all
.protofiles inprotos/<contractDir>/for field names and types
Method classification
| Condition | Type | Android return | Notes |
|---|---|---|---|
hybridToNative only |
fire-and-forget | Unit |
Delegates to plugin-base Helper |
| both directions, JS-initiated | promise | Promise param |
Delegates with listener |
nativeToHybrid only, SDK-initiated |
event | emit via EventEmitter | PayloadGenerator + EventEmitterImpl |
Build a complete method table before writing any code.
Phase 3 — Android Bridge Implementation
3.1 Create branch
cd React-Native
git fetch
git checkout -b feature/<ticketId>-<contractSuffix>
3.2 Check if SDK module already exists
ls <rnSdkDir>/android/src/main/ 2>/dev/null
- Not found → scaffold full module (Steps 3.3–3.9)
- Found → read existing files first, then add only the missing methods
When a module already exists, some of its existing files (e.g. sdk/core) predate the
Cards-based standard (single MoEReactBridge.kt/MoEReactBridgeHandler.kt, no newarch/
oldarch split, no per-module Package.kt). In that case follow the file's own existing
pattern instead of forcing the Cards-based file split — read every existing file in the module
before writing anything.
Append new methods/cases at the bottom of the file, not interleaved among existing ones.
For every file you extend (MoEReactBridge.kt, MoEReactBridgeHandler.kt, Constants.kt):
add each new method as the last member before the closing brace (or, for MoEReactBridgeHandler.kt,
immediately before a trailing companion object if one exists), not next to the nearest
similar-looking existing method. For EventEmitterImpl.kt's when (event.eventType) block,
add the new case immediately before the else branch (not next to a similar existing case);
add the new emitXxx() private function as the last function in the class; add the new
EventType to "..." pair as the last entry in the eventMapping map.
3.3 Android build config
<rnSdkDir>/android/build.gradle
Copy sdk/cards/android/build.gradle exactly, then change:
moengageNativeBOMVersion→<android_bom_version>namespace→"<rnPackage>"api("com.moengage:cards")→api("com.moengage:<featureSdkArtifact>")(ask user if unknown)implementation("com.moengage:plugin-base-cards")→implementation("com.moengage:<androidModuleName>")
3.4 Constants.kt
→ See examples/android/Constants.kt
Generate at: <rnSdkDir>/android/src/main/java/<rnPackage>/Constants.kt
Constants must include:
- One string constant per method name (camelCase), matching the contract file names exactly
- One string constant per event name (for nativeToHybrid events)
3.5 BridgeHandler.kt
→ See examples/android/BridgeHandler.kt
Generate at: <rnSdkDir>/android/src/main/java/<rnPackage>/<rnBridgeName>Handler.kt
Rules:
- Fire-and-forget methods: call
PluginHelper(<featureNameCamel>Helper).methodName(context, payload)— no return - Promise methods, listener-based: create a listener, call the helper, resolve/reject from the callback
- Promise methods,
MoETask-based: callpluginHelper.methodName(context, payload)and chain.onSuccess { result -> promise.resolve(...) }.onFailure { failure -> promise.reject(failure.reason.toString(), failure.message) }directly on the returned task — don't wrap it in another listener - Building the resolved/rejected payload: if plugin-base exposes a
xxxToJson/xxxResultToJsonmapper for the result type (checkUtils.ktin the target plugin-base module first), call that instead of hand-rolling the response JSON withJSONObject/buildJsonObjectin the bridge — same rule as fire-and-forget/event payloads, now applying to promise responses too. If the mapper needs to echo backaccountMetafrom the request, prefer a plugin-base overload that takes the raw requestpayload: Stringand parses it internally over re-parsing it yourself in the bridge — avoids a second JSON-parse step and keeps the bridge free oforg.json/kotlinx parsing calls entirely for that method. - Event methods: not in BridgeHandler — handled by EventEmitterImpl + PayloadGenerator
- Each method is annotated
@ReactMethod(old arch); new arch goes in NewArchBridge - Log all entry points using the module logger
3.6 EventEmitterImpl.kt (only if event methods exist)
→ See examples/android/EventEmitterImpl.kt
Generate at: <rnSdkDir>/android/src/main/java/<rnPackage>/EventEmitterImpl.kt
Implements the plugin-base EventEmitter interface. The emit(event) method switches on
event.eventType and calls the React-Native sendEvent for each known event type.
3.7 PayloadGenerator.kt (only if event methods exist)
→ See examples/android/PayloadGenerator.kt
Generate at: <rnSdkDir>/android/src/main/java/<rnPackage>/PayloadGenerator.kt
Contains one toWritableMap() extension function per nativeToHybrid event type. Converts the
plugin-base event's payload into a WritableMap for React-Native emission.
3.8 Package.kt
→ See examples/android/Package.kt
Generate at: <rnSdkDir>/android/src/main/java/<rnPackage>/MoEngage<featureNameCamel>Package.kt
3.9 Arch bridges
New arch → examples/android/NewArchBridge.kt
Path: <rnSdkDir>/android/src/newarch/java/<rnPackage>/MoEngage<featureNameCamel>Bridge.kt
Old arch → examples/android/OldArchBridge.kt
Path: <rnSdkDir>/android/src/oldarch/java/<rnPackage>/MoEngage<featureNameCamel>Bridge.kt
Rules:
- New arch: implements the TurboModule spec; delegates every method to the BridgeHandler
- Old arch: extends
ReactContextBaseJavaModule; delegates every method to the BridgeHandler - Both arches expose exactly the same public method signatures
3.10 Commit
git add <rnSdkDir>/android/
git commit -m "<ticketId>: Add React-Native Android bridge for <featureName>"
Phase 4 — Create Pull Request
git push -u origin feature/<ticketId>-<contractSuffix>
gh pr create \
--title "<ticketId>: Add React-Native Android bridge for <featureName>" \
--base development \
--body "$(cat <<'EOF'
## Summary
- Adds Android Kotlin bridge (`<rnSdkDir>/android/`) for the <featureName> feature
- BridgeHandler delegates to `<androidModuleName>` plugin-base helper
- New-arch (TurboModule) + old-arch (ReactMethod) bridges implemented
- Android BOM version: <android_bom_version>
## Related PRs
- android-plugin-base: <android_plugin_base_pr_url>
## Contract
Branch: `<contract_branch>` in mobile-sdk-contracts
## Methods
| Method | Type |
|---|---|
<table rows from method table>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
Phase 5 — Report & Hand-off
Print:
- PR URL
- Full method table (name, type, Android delegate)
- All
// TODOitems left for manual verification - List of all files created or modified
SampleApp wiring is out of scope for this skill unless explicitly asked for. If the user does
ask for it, add manual-test entries by mirroring an existing sibling entry exactly: one button per
new method in the relevant screen's data array (e.g. SampleApp/PushNotification.js,
SampleApp/HomeScreen.js — same {id, title, action} shape as neighboring entries; a
Promise-returning method's action is async and shows the result, e.g. via Alert.alert), and one
ReactMoE.setEventListener(...) registration per new event in SampleApp/App.js alongside the
existing ones.
Then ask the user:
"Android bridge for
<featureName>is done (PR: <pr_url>). Would you like to also run the TypeScript layer now (react-native-ts-implementation)? It needs the same contract branch, BOM version, and both PR URLs."
If the user says yes, remind them to invoke:
/react-native-ts-implementation
ticket_id: <ticketId>
feature_description: <feature_description>
contract_branch: <contract_branch>
android_bom_version: <android_bom_version>
plugin_base_pr_url: <plugin_base_pr_url>
android_bridge_pr_url: <this PR URL>
Codebase Reference Files
Read these before generating the corresponding output — copy copyright headers, logging conventions, and structural patterns exactly.
| What | Codebase path |
|---|---|
| BridgeHandler reference | React-Native/sdk/cards/android/src/main/java/.../MoEngageCardsBridgeHandler.kt |
| EventEmitterImpl reference | React-Native/sdk/cards/android/src/main/java/.../EventEmitterImpl.kt |
| PayloadGenerator reference | React-Native/sdk/cards/android/src/main/java/.../PayloadGenerator.kt |
| Package reference | React-Native/sdk/cards/android/src/main/java/.../MoEngageCardsPackage.kt |
| New-arch bridge reference | React-Native/sdk/cards/android/src/newarch/.../MoEngageCardsBridge.kt |
| Old-arch bridge reference | React-Native/sdk/cards/android/src/oldarch/.../MoEngageCardsBridge.kt |
| build.gradle reference | React-Native/sdk/cards/android/build.gradle |
| plugin-base module reference | ../android-plugin-base/<androidModuleName>/ (output of plugin-base-feature-implementation) |
| plugin-base JSON mappers | ../android-plugin-base/<androidModuleName>/src/main/java/.../Utils.kt (or module-specific Utils.kt/PayloadMapper.kt) — check for an existing xxxToJson/xxxResultToJson for the method's result type before writing one in the bridge |
Error Handling Rules
contract_branchnot found in../mobile-sdk-contracts→ stop and tell the usercontractDirnot found injson/hybridToNative/→ list available dirs and askrnSdkDiralready hasandroid/src/main/→ read existing files, add only missing methods- SDK artifact name or plugin-base helper class name unknown → add
// TODO: verifyand continue - Push fails → report error and local branch name so the user can push manually