modules/expo-background-notification-handler/README.md
A custom Expo module for managing shared notification preferences and handling background notifications in the Bluesky Social app. This module enables communication between the main app and notification service extensions through shared storage.
This module solves a critical problem in native notification handling: notification service extensions run in a separate process from the main app and cannot directly access React Native state or APIs. The module provides a bridge by storing notification preferences in shared storage that both the main app and notification service extension can access.
The primary use case is storing user preferences (like notification sound settings) while the app is foregrounded or backgrounded, minimizing the need for background fetches when processing notifications.
Uses iOS App Groups (group.app.bsky) to share UserDefaults between the main app and the notification service extension. This allows the notification service extension to read preferences set by the main app without launching the app.
Key Files:
ios/ExpoBackgroundNotificationHandlerModule.swift - Native module implementationios/ExpoBackgroundNotificationHandler.podspec - CocoaPods specificationUses SharedPreferences with Firebase Cloud Messaging (FCM) to handle background notifications. The module tracks app foreground/background state and conditionally processes notifications based on whether the app is foregrounded.
Key Files:
android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt - Expo module definitionandroid/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt - SharedPreferences wrapperandroid/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt - Notification processing logicandroid/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt - Interface for showing notificationsandroid/build.gradle - Build configurationKey Files:
index.ts - Module entry pointsrc/ExpoBackgroundNotificationHandlerModule.ts - Native module binding (iOS/Android)src/ExpoBackgroundNotificationHandlerModule.web.ts - Web stubsrc/ExpoBackgroundNotificationHandler.types.ts - TypeScript type definitionssrc/BackgroundNotificationHandlerProvider.tsx - React Context provider for preferencesThe module manages the following notification preferences:
{
playSoundChat: boolean, // Currently exposed to TypeScript
playSoundFollow: boolean, // Native only (not yet exposed)
playSoundLike: boolean, // Native only (not yet exposed)
playSoundMention: boolean, // Native only (not yet exposed)
playSoundQuote: boolean, // Native only (not yet exposed)
playSoundReply: boolean, // Native only (not yet exposed)
playSoundRepost: boolean, // Native only (not yet exposed)
mutedThreads: [String: [String]], // iOS only
badgeCount: number // iOS only
}
Default values are initialized when the module is created, with most sound preferences defaulting to false except playSoundChat which defaults to true.
// Get all preferences
getAllPrefsAsync(): Promise<BackgroundNotificationHandlerPreferences>
// Get individual values
getBoolAsync(forKey: string): Promise<boolean>
getStringAsync(forKey: string): Promise<string>
getStringArrayAsync(forKey: string): Promise<string[]>
// Set individual values
setBoolAsync(forKey: string, value: boolean): Promise<void>
setStringAsync(forKey: string, value: string): Promise<void>
setStringArrayAsync(forKey: string, value: string[]): Promise<void>
// Array manipulation
addToStringArrayAsync(forKey: string, value: string): Promise<void>
removeFromStringArrayAsync(forKey: string, value: string): Promise<void>
addManyToStringArrayAsync(forKey: string, value: string[]): Promise<void>
removeManyFromStringArrayAsync(forKey: string, value: string[]): Promise<void>
// Badge count (iOS only)
setBadgeCountAsync(count: number): Promise<void>
The module provides a React Context provider for managing preferences in the app:
import {
BackgroundNotificationPreferencesProvider,
useBackgroundNotificationPreferences,
} from 'expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
function App() {
return (
<BackgroundNotificationPreferencesProvider>
<YourApp />
</BackgroundNotificationPreferencesProvider>
)
}
function SettingsScreen() {
const {preferences, setPref} = useBackgroundNotificationPreferences()
return (
<Toggle
value={preferences.playSoundChat}
onValueChange={(value) => setPref('playSoundChat', value)}
/>
)
}
The Android implementation includes logic for processing notifications while the app is backgrounded:
Chat messages: Applies custom notification channels based on playSoundChat preference
chat-messages channel (or dm.mp3 sound on older Android)chat-messages-muted channelOther notification types: On Android Oreo+ (API 26+), assigns notifications to channels based on reason:
like, repost, follow, mention, reply, quote, like-via-repost, repost-via-repost, subscribed-postWhen the app is foregrounded, the module defers to expo-notifications for notification handling.
Requires App Group entitlement configured in Xcode:
group.app.bskyRequires Firebase Cloud Messaging (FCM) integration:
com.google.firebase:firebase-messaging-ktx:24.0.0xyz.blueskyweb.appThe module is used to:
By keeping preferences in shared storage, the notification service extension can make intelligent decisions about notification presentation without waking up the React Native runtime or making network requests.