Skip to content

feat: Add Android push notification support to Expo config plugin#381

Closed
Rosie-Kennelly-1 wants to merge 8 commits intomainfrom
rosie/android-expo-push-notifications
Closed

feat: Add Android push notification support to Expo config plugin#381
Rosie-Kennelly-1 wants to merge 8 commits intomainfrom
rosie/android-expo-push-notifications

Conversation

@Rosie-Kennelly-1
Copy link

@Rosie-Kennelly-1 Rosie-Kennelly-1 commented Mar 5, 2026

Why?

The Expo config plugin automates iOS push notification setup (since PR #191) but has no Android equivalent. This means Expo developers' end users silently never receive push notifications on Android when a support agent replies to their conversation — and the only workaround is manually creating native Kotlin files, which defeats the purpose of using Expo.

How?

A new config plugin generates a Kotlin FirebaseMessagingService at expo prebuild time and registers it in AndroidManifest.xml, following the exact pattern already proven in the with-notifications example. The service routes Intercom pushes to the SDK and passes non-Intercom messages through for other handlers (e.g. expo-notifications). The plugin also conditionally adds firebase-messaging to the app module's build.gradle when not already present — needed because the native module declares it as implementation (private to the library), but the generated service in the app module requires it on its compile classpath.

Also fixes a pre-existing bug in withPushNotifications.ts where config was passed instead of newConfig to appDelegate and infoPlist, breaking the plugin chaining.

Decisions

  • Always applied (not opt-in) — matches how iOS push setup works today. The plugin runs unconditionally when @intercom/intercom-react-native is in your Expo plugins.
  • android:exported="false" on the service — more secure than the "true" used in the with-notifications example. Firebase delivers messages internally; the service doesn't need to be externally accessible.
  • android:priority="10" on the intent filter — explicitly ensures Intercom's service handles FCM events before lower-priority services like expo-notifications (which registers at -1).
  • Conditional gradle dependencyfirebase-messaging:24.1.2 is added to the app's build.gradle only if not already present. The native module declares it as implementation (private), so the app module needs its own copy for the generated Kotlin to compile.
  • Idempotent — running expo prebuild multiple times won't duplicate the manifest service entry or the gradle dependency.
  • Existing FCM service detection — if the manifest already has a FirebaseMessagingService registered (from manual setup or another SDK), the plugin skips registration and warns. The generated .kt file is still written since withDangerousMod (file generation) runs before withAndroidManifest (conflict detection) in Expo's mod pipeline, and cross-mod dependencies would add unnecessary complexity. The orphaned file is harmless — it's an unregistered source file in the ephemeral android/ directory that expo prebuild regenerates from scratch.

Generated with Claude Code

The Expo config plugin already automated iOS push notification setup
(PR #191) but Android was left out, requiring developers to manually
create a FirebaseMessagingService and edit native files — defeating
the purpose of using Expo.

This adds the Android counterpart: a config plugin that generates a
Kotlin FirebaseMessagingService at prebuild time, registers it in the
AndroidManifest, and routes Intercom pushes to the SDK while passing
non-Intercom messages through to other handlers (e.g. expo-notifications).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rosie-Kennelly-1 and others added 3 commits March 5, 2026 14:29
- Add explicit android:priority="10" on intent filter
- Add utf-8 encoding to writeFileSync for generated Kotlin source
- Add test for preserving pre-existing services in AndroidManifest
- Exclude __tests__ from tsconfig to match existing JS test convention
- Remove unnecessary comments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If a developer already has a FirebaseMessagingService registered (from
manual setup or another SDK), adding a second one would cause
unpredictable FCM routing. The plugin now detects this and skips
registration with a warning explaining how to route Intercom pushes
manually.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Apply prettier formatting fixes
- Exclude __tests__ from tsconfig.build.json so bob build doesn't try
  to generate type definitions for test files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The native module declares firebase-messaging as an `implementation`
dependency, which is private to the library module. Since the generated
FirebaseMessagingService lives in the app module, it needs
firebase-messaging on its own compile classpath. Conditionally adds the
dependency to app/build.gradle if not already present.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rosie-Kennelly-1 and others added 3 commits March 6, 2026 11:01
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ild time

Instead of hardcoding the firebase-messaging version, read it from the
native module's android/build.gradle so the app dependency stays in
sync automatically when the SDK is updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
__dirname resolves differently from src/ vs lib/commonjs/ after
compilation. Using require.resolve to find the package.json ensures
the native module's android/build.gradle is found correctly regardless
of the build output directory structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Rosie-Kennelly-1
Copy link
Author

Superseded by a stacked PR series for easier review:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants