Skip to main content

🔔 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

🎯 Next Steps