🔔 Push Notifications
This document covers the comprehensive push notification system implemented in the Com DEALL mobile application, including setup, configuration, and implementation details.
🎯 Overview
The mobile app implements a robust push notification system using Firebase Cloud Messaging (FCM) and Expo Notifications to deliver real-time updates to users across iOS and Android platforms.
🏗️ Notification Architecture
System Components
Firebase Cloud Messaging (FCM)
├── Server-side Notification Service
├── Client-side Notification Handler
├── Background Message Processing
└── Foreground Message Display
Notification Flow
Server Event → FCM → Device → Expo Notifications → App Display
↓ ↓ ↓ ↓ ↓
Trigger → Cloud Service → Push → Native Handler → User Interface
🔧 Technical Implementation
Firebase Configuration
iOS Configuration
// GoogleService-Info.plist configuration
{
"CLIENT_ID": "your-client-id",
"REVERSED_CLIENT_ID": "your-reversed-client-id",
"API_KEY": "your-api-key",
"GCM_SENDER_ID": "your-sender-id",
"PLIST_VERSION": "1",
"BUNDLE_ID": "com.comdeall.communicaids",
"PROJECT_ID": "your-project-id",
"STORAGE_BUCKET": "your-storage-bucket",
"IS_ADS_ENABLED": false,
"IS_ANALYTICS_ENABLED": true,
"IS_APPINVITE_ENABLED": true,
"IS_GCM_ENABLED": true,
"IS_SIGNIN_ENABLED": true,
"GOOGLE_APP_ID": "your-app-id"
}
Android Configuration
// google-services.json configuration
{
"project_info": {
"project_number": "your-project-number",
"project_id": "your-project-id"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "your-mobile-sdk-app-id",
"android_client_info": {
"package_name": "com.comdeall.communicaids"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "your-api-key"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
]
}
Notification Handler Setup
// App.tsx - Notification handler configuration
import messaging from '@react-native-firebase/messaging';
import * as Notifications from 'expo-notifications';
// Configure notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
// Handle background messages
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Background message received:', remoteMessage);
// Process background message
});
// Handle foreground messages
useEffect(() => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('channel-id', {
name: 'My channel',
importance: Notifications.AndroidImportance.HIGH,
});
}
await Notifications.scheduleNotificationAsync({
content: {
title: remoteMessage?.notification?.title,
body: remoteMessage?.notification?.body,
},
trigger: null,
});
});
return unsubscribe;
}, []);
📱 Notification Types
1. Chat Notifications
- Trigger: New message received
- Content: Sender name, message preview
- Action: Navigate to chat screen
- Priority: High
// Chat notification payload
{
"notification": {
"title": "New message from Dr. Smith",
"body": "How is the therapy session going?",
"icon": "ic_notification",
"sound": "default"
},
"data": {
"type": "chat",
"sender_id": "user123",
"chat_id": "chat456",
"action": "open_chat"
}
}
2. Appointment Notifications
- Trigger: Appointment reminders, bookings, cancellations
- Content: Appointment details, time, therapist name
- Action: Navigate to appointment details
- Priority: High
// Appointment notification payload
{
"notification": {
"title": "Appointment Reminder",
"body": "Your appointment with Dr. Smith is in 1 hour",
"icon": "ic_appointment",
"sound": "default"
},
"data": {
"type": "appointment",
"appointment_id": "apt789",
"action": "open_appointment"
}
}
3. Assessment Notifications
- Trigger: Assessment completion, new assessments
- Content: Assessment status, completion details
- Action: Navigate to assessment details
- Priority: Medium
// Assessment notification payload
{
"notification": {
"title": "Assessment Completed",
"body": "Your child's assessment report is ready",
"icon": "ic_assessment",
"sound": "default"
},
"data": {
"type": "assessment",
"assessment_id": "assess123",
"action": "open_assessment"
}
}
4. Lesson Plan Notifications
- Trigger: New lesson plan assignments, updates
- Content: Lesson plan details, therapist notes
- Action: Navigate to lesson plan
- Priority: Medium
// Lesson plan notification payload
{
"notification": {
"title": "New Lesson Plan",
"body": "Dr. Smith has assigned a new lesson plan",
"icon": "ic_lesson_plan",
"sound": "default"
},
"data": {
"type": "lesson_plan",
"lesson_plan_id": "lp456",
"action": "open_lesson_plan"
}
}
5. Subscription Notifications
- Trigger: Payment confirmations, renewals, failures
- Content: Payment status, subscription details
- Action: Navigate to subscription management
- Priority: High
// Subscription notification payload
{
"notification": {
"title": "Payment Successful",
"body": "Your subscription has been renewed",
"icon": "ic_payment",
"sound": "default"
},
"data": {
"type": "subscription",
"subscription_id": "sub789",
"action": "open_subscription"
}
}
🔧 Notification Configuration
Permission Handling
// Request notification permissions
const requestNotificationPermissions = async () => {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
console.log('Notification permissions not granted');
return false;
}
return true;
};
FCM Token Management
// Get and store FCM token
const getFCMToken = async () => {
try {
const token = await messaging().getToken();
console.log('FCM Token:', token);
// Store token in backend
await storeFCMToken(token);
return token;
} catch (error) {
console.error('Error getting FCM token:', error);
return null;
}
};
// Store FCM token in backend
const storeFCMToken = async (token: string) => {
try {
await updateUserFCMToken({
variables: {
fcm_token: token,
user_id: userId
}
});
} catch (error) {
console.error('Error storing FCM token:', error);
}
};
Notification Channels (Android)
// Create notification channels
const createNotificationChannels = async () => {
// Chat notifications
await Notifications.setNotificationChannelAsync('chat', {
name: 'Chat Messages',
importance: Notifications.AndroidImportance.HIGH,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
// Appointment notifications
await Notifications.setNotificationChannelAsync('appointment', {
name: 'Appointments',
importance: Notifications.AndroidImportance.HIGH,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
// Assessment notifications
await Notifications.setNotificationChannelAsync('assessment', {
name: 'Assessments',
importance: Notifications.AndroidImportance.DEFAULT,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
};
🎯 Notification Handling
Foreground Notifications
// Handle foreground notifications
const handleForegroundNotification = async (remoteMessage: any) => {
const { notification, data } = remoteMessage;
// Show local notification
await Notifications.scheduleNotificationAsync({
content: {
title: notification?.title,
body: notification?.body,
data: data,
},
trigger: null,
});
// Handle notification data
if (data?.action) {
handleNotificationAction(data);
}
};
Background Notifications
// Handle background notifications
const handleBackgroundNotification = async (remoteMessage: any) => {
console.log('Background notification received:', remoteMessage);
// Process notification data
const { data } = remoteMessage;
if (data?.type === 'chat') {
// Update chat state
updateChatState(data);
} else if (data?.type === 'appointment') {
// Update appointment state
updateAppointmentState(data);
}
// Store notification for later processing
storeNotification(remoteMessage);
};
Notification Actions
// Handle notification actions
const handleNotificationAction = (data: any) => {
const { type, action, ...params } = data;
switch (type) {
case 'chat':
if (action === 'open_chat') {
navigation.navigate('Chat', { chatId: params.chat_id });
}
break;
case 'appointment':
if (action === 'open_appointment') {
navigation.navigate('Appointment', { appointmentId: params.appointment_id });
}
break;
case 'assessment':
if (action === 'open_assessment') {
navigation.navigate('Assessment', { assessmentId: params.assessment_id });
}
break;
case 'lesson_plan':
if (action === 'open_lesson_plan') {
navigation.navigate('LessonPlan', { lessonPlanId: params.lesson_plan_id });
}
break;
case 'subscription':
if (action === 'open_subscription') {
navigation.navigate('Profile', { screen: 'Subscription' });
}
break;
}
};
📊 Notification Analytics
Tracking Notification Events
// Track notification events
const trackNotificationEvent = (event: string, data: any) => {
// Track notification received
if (event === 'notification_received') {
analytics.track('notification_received', {
notification_type: data.type,
timestamp: new Date().toISOString(),
});
}
// Track notification opened
if (event === 'notification_opened') {
analytics.track('notification_opened', {
notification_type: data.type,
action: data.action,
timestamp: new Date().toISOString(),
});
}
};
Notification Metrics
// Notification metrics
interface NotificationMetrics {
totalNotifications: number;
openedNotifications: number;
dismissedNotifications: number;
notificationTypes: {
chat: number;
appointment: number;
assessment: number;
lesson_plan: number;
subscription: number;
};
openRate: number;
dismissRate: number;
}
🔧 Server-side Integration
Notification Service
// Server-side notification service
class NotificationService {
async sendChatNotification(userId: string, message: string, senderName: string) {
const payload = {
notification: {
title: `New message from ${senderName}`,
body: message,
icon: 'ic_notification',
sound: 'default'
},
data: {
type: 'chat',
sender_id: senderId,
chat_id: chatId,
action: 'open_chat'
}
};
await this.sendNotification(userId, payload);
}
async sendAppointmentNotification(userId: string, appointment: any) {
const payload = {
notification: {
title: 'Appointment Reminder',
body: `Your appointment with ${appointment.therapistName} is in 1 hour`,
icon: 'ic_appointment',
sound: 'default'
},
data: {
type: 'appointment',
appointment_id: appointment.id,
action: 'open_appointment'
}
};
await this.sendNotification(userId, payload);
}
}
🎯 Best Practices
Notification Design
- Clear Titles: Descriptive and actionable titles
- Concise Body: Brief and informative content
- Appropriate Icons: Visual indicators for notification type
- Sound Selection: Appropriate notification sounds
User Experience
- Timing: Send notifications at appropriate times
- Frequency: Avoid notification spam
- Relevance: Only send relevant notifications
- Personalization: Customize based on user preferences
Performance
- Batch Processing: Group similar notifications
- Rate Limiting: Prevent notification flooding
- Error Handling: Graceful failure handling
- Analytics: Track notification performance