React Native course ยท Module 7: Notifications

Project: Atlantis Notification System

2 min read ยท

Let's create a complete notification system!

import { useEffect, useRef, useState } from 'react';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform } from 'react-native';

// Configuration
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowBanner: true,
    shouldShowList: true,
    shouldPlaySound: true,
    shouldSetBadge: true,
  }),
});

// Notifications hook
export function useNotifications() {
  const [expoPushToken, setExpoPushToken] = useState<string>('');
  const [notification, setNotification] = useState<Notifications.Notification>();
  const notificationListener = useRef<Notifications.Subscription>();
  const responseListener = useRef<Notifications.Subscription>();

  useEffect(() => {
    // Registration
    registerForPushNotificationsAsync().then((token) => {
      if (token) setExpoPushToken(token);
    });

    // Setup Android channels
    setupAndroidChannels();

    // Listeners
    notificationListener.current = Notifications.addNotificationReceivedListener(
      setNotification
    );

    responseListener.current = Notifications.addNotificationResponseReceivedListener(
      (response) => {
        handleNotificationResponse(response);
      }
    );

    return () => {
      notificationListener.current?.remove();
      responseListener.current?.remove();
    };
  }, []);

  return { expoPushToken, notification };
}

async function registerForPushNotificationsAsync() {
  if (!Device.isDevice) {
    console.log('Push notifications require a device');
    return;
  }

  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;

  if (existingStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }

  if (finalStatus !== 'granted') {
    return;
  }

  const token = await Notifications.getExpoPushTokenAsync();
  return token.data;
}

async function setupAndroidChannels() {
  if (Platform.OS !== 'android') return;

  await Notifications.setNotificationChannelAsync('default', {
    name: 'Default',
    importance: Notifications.AndroidImportance.MAX,
  });

  await Notifications.setNotificationChannelAsync('reminders', {
    name: 'Reminders',
    importance: Notifications.AndroidImportance.HIGH,
  });
}

function handleNotificationResponse(response: Notifications.NotificationResponse) {
  const data = response.notification.request.content.data;
  // Navigate based on data
  console.log('Notification data:', data);
}

// Notification services
export const NotificationService = {
  // Local notification
  sendLocal: async (title: string, body: string, data?: object) => {
    return Notifications.scheduleNotificationAsync({
      content: { title, body, data, sound: true },
      trigger: null,
    });
  },

  // Reminder
  scheduleReminder: async (title: string, body: string, date: Date) => {
    return Notifications.scheduleNotificationAsync({
      content: { title, body },
      trigger: date,
    });
  },

  // Daily reminder
  scheduleDailyReminder: async (title: string, body: string, hour: number, minute: number) => {
    return Notifications.scheduleNotificationAsync({
      content: { title, body },
      trigger: { hour, minute, repeats: true },
    });
  },

  // Cancel all
  cancelAll: async () => {
    await Notifications.cancelAllScheduledNotificationsAsync();
  },

  // Clear badge
  clearBadge: async () => {
    await Notifications.setBadgeCountAsync(0);
  },
};

This system handles registration, local notifications, push notifications, and Android channels!

Code for this lesson: App.tsx
import { View, Text, Button, StyleSheet } from 'react-native';
import * as Notifications from 'expo-notifications';

// Project: complete Atlantis notification service
export const NotificationService = {
  // Immediate local notification
  sendLocal: async (title: string, body: string, data?: object) => {
    // TODO: call scheduleNotificationAsync with trigger: null
    return Notifications.scheduleNotificationAsync({
      content: { title, body, data, sound: true },
      trigger: null,
    });
  },

  // Reminder on a specific date
  scheduleReminder: async (title: string, body: string, date: Date) => {
    // TODO: set the trigger to the passed date
    return Notifications.scheduleNotificationAsync({
      content: { title, body },
      trigger: date,
    });
  },

  // Daily reminder at a given time
  scheduleDailyReminder: async (title: string, body: string, hour: number, minute: number) => {
    // TODO: set the trigger to { hour, minute, repeats: true }
    return Notifications.scheduleNotificationAsync({
      content: { title, body },
      // TODO: complete the trigger with repeats: true
      trigger: { hour, minute, repeats: false },
    });
  },

  // Cancel all scheduled notifications
  cancelAll: async () => {
    // TODO: call cancelAllScheduledNotificationsAsync()
  },

  // Clear the app icon badge
  clearBadge: async () => {
    // TODO: set the badge to 0 with setBadgeCountAsync(0)
  },
};

export default function App() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Atlantis Notification System</Text>
      <Button
        title="Send local"
        onPress={() => NotificationService.sendLocal('Atlantis', 'Poseidon summons you!')}
      />
      <Button title="Clear badge" onPress={NotificationService.clearBadge} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#1a5276', gap: 16, padding: 20 },
  title: { fontSize: 22, color: '#fff', textAlign: 'center' },
});

Docs entries used in this lesson

Spotted a mistake in this lesson?

Hands-on tasks in the game

  • Code editor

    Complete the code of the complete service.

Useful articles