forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1# expo-background-notification-handler
2
3A 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.
4
5## Purpose
6
7This 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.
8
9The 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.
10
11## Platform Support
12
13- **iOS**: Full support via UserDefaults with App Groups
14- **Android**: Full support via SharedPreferences
15- **Web**: Stub implementation (no-op)
16
17## Architecture
18
19### iOS Implementation
20
21Uses 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.
22
23**Key Files:**
24- `ios/ExpoBackgroundNotificationHandlerModule.swift` - Native module implementation
25- `ios/ExpoBackgroundNotificationHandler.podspec` - CocoaPods specification
26
27### Android Implementation
28
29Uses 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.
30
31**Key Files:**
32- `android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt` - Expo module definition
33- `android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt` - SharedPreferences wrapper
34- `android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt` - Notification processing logic
35- `android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt` - Interface for showing notifications
36- `android/build.gradle` - Build configuration
37
38### TypeScript/React API
39
40**Key Files:**
41- `index.ts` - Module entry point
42- `src/ExpoBackgroundNotificationHandlerModule.ts` - Native module binding (iOS/Android)
43- `src/ExpoBackgroundNotificationHandlerModule.web.ts` - Web stub
44- `src/ExpoBackgroundNotificationHandler.types.ts` - TypeScript type definitions
45- `src/BackgroundNotificationHandlerProvider.tsx` - React Context provider for preferences
46
47## Stored Preferences
48
49The module manages the following notification preferences:
50
51```typescript
52{
53 playSoundChat: boolean, // Currently exposed to TypeScript
54 playSoundFollow: boolean, // Native only (not yet exposed)
55 playSoundLike: boolean, // Native only (not yet exposed)
56 playSoundMention: boolean, // Native only (not yet exposed)
57 playSoundQuote: boolean, // Native only (not yet exposed)
58 playSoundReply: boolean, // Native only (not yet exposed)
59 playSoundRepost: boolean, // Native only (not yet exposed)
60 mutedThreads: [String: [String]], // iOS only
61 badgeCount: number // iOS only
62}
63```
64
65Default values are initialized when the module is created, with most sound preferences defaulting to `false` except `playSoundChat` which defaults to `true`.
66
67## API
68
69### Core Methods
70
71```typescript
72// Get all preferences
73getAllPrefsAsync(): Promise<BackgroundNotificationHandlerPreferences>
74
75// Get individual values
76getBoolAsync(forKey: string): Promise<boolean>
77getStringAsync(forKey: string): Promise<string>
78getStringArrayAsync(forKey: string): Promise<string[]>
79
80// Set individual values
81setBoolAsync(forKey: string, value: boolean): Promise<void>
82setStringAsync(forKey: string, value: string): Promise<void>
83setStringArrayAsync(forKey: string, value: string[]): Promise<void>
84
85// Array manipulation
86addToStringArrayAsync(forKey: string, value: string): Promise<void>
87removeFromStringArrayAsync(forKey: string, value: string): Promise<void>
88addManyToStringArrayAsync(forKey: string, value: string[]): Promise<void>
89removeManyFromStringArrayAsync(forKey: string, value: string[]): Promise<void>
90
91// Badge count (iOS only)
92setBadgeCountAsync(count: number): Promise<void>
93```
94
95### React Context API
96
97The module provides a React Context provider for managing preferences in the app:
98
99```typescript
100import {
101 BackgroundNotificationPreferencesProvider,
102 useBackgroundNotificationPreferences,
103} from 'expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
104
105function App() {
106 return (
107 <BackgroundNotificationPreferencesProvider>
108 <YourApp />
109 </BackgroundNotificationPreferencesProvider>
110 )
111}
112
113function SettingsScreen() {
114 const {preferences, setPref} = useBackgroundNotificationPreferences()
115
116 return (
117 <Toggle
118 value={preferences.playSoundChat}
119 onValueChange={(value) => setPref('playSoundChat', value)}
120 />
121 )
122}
123```
124
125## Android Notification Handling
126
127The Android implementation includes logic for processing notifications while the app is backgrounded:
128
129- **Chat messages**: Applies custom notification channels based on `playSoundChat` preference
130 - Sound enabled: Uses `chat-messages` channel (or `dm.mp3` sound on older Android)
131 - Sound disabled: Uses `chat-messages-muted` channel
132
133- **Other notification types**: On Android Oreo+ (API 26+), assigns notifications to channels based on reason:
134 - Supported reasons: `like`, `repost`, `follow`, `mention`, `reply`, `quote`, `like-via-repost`, `repost-via-repost`, `subscribed-post`
135 - Each reason maps to its corresponding notification channel
136
137When the app is foregrounded, the module defers to `expo-notifications` for notification handling.
138
139## Configuration
140
141### iOS
142
143Requires App Group entitlement configured in Xcode:
144- App Group ID: `group.app.bsky`
145
146### Android
147
148Requires Firebase Cloud Messaging (FCM) integration:
149- Dependency: `com.google.firebase:firebase-messaging-ktx:24.0.0`
150- SharedPreferences name: `xyz.blueskyweb.app`
151
152## Usage in the App
153
154The module is used to:
155
1561. Store notification preferences that need to be accessed by notification service extensions
1572. Track app foreground/background state on Android
1583. Process and mutate notification payloads based on user preferences before display
1594. Manage notification badge counts on iOS
1605. Handle thread muting and other notification filtering logic
161
162By 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.