Course Dashboard

Lesson 5: Interaction & Response — Beyond the Banner

Sending a notification is only half the battle. The true power of engagement lies in what happens after the user taps the banner. A professional app shouldn't just open the home screen; it should navigate the user to the exact context of the notification—a process known as Deep Linking.

In this lesson, we will master:

  • The App Lifecycle: Handling Foreground, Background, and "Killed" states.
  • Notification Response Listeners: Capturing user taps in real-time.
  • Extracting Payloads: Pulling custom data from the notification.
  • The "Cold Start" Problem: Handling interactions when the app is completely closed.

The Three States of Engagement

When a user interacts with a notification, your app might be in one of three states:

  1. Foreground: The user is actively using the app.
  2. Background: The app is minimized but still in memory.
  3. Killed (Cold Start): The app is not running at all.

Your notification logic must be robust enough to handle all three scenarios to ensure a seamless "Deep Linking" experience.


1. Capturing Taps with Listeners

The primary way to detect a user's interaction is the addNotificationResponseReceivedListener. This listener should ideally be placed at the root level of your application (e.g., in App.tsx or a root layout).

index.tstypescript
import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';

export default function RootLayout() {
  useEffect(() => {
    // This fires whenever a user taps a notification
    const subscription = Notifications.addNotificationResponseReceivedListener(response => {
      const { screen, itemId } = response.notification.request.content.data;
      
      console.log(`User tapped notification for screen: ${screen}`);
      
      // Example: Navigate to specific screen
      if (screen === 'Details') {
        // navigation.navigate('Details', { id: itemId });
      }
    });

    return () => subscription.remove();
  }, []);
}

2. Handling the "Killed" State (Cold Starts)

If the app was completely closed (Killed) when the user tapped the notification, the listener we defined above might not be ready in time to catch the event.

To solve this, we use getLastNotificationResponseAsync during the app's initialization phase.

index.tstypescript
const handleInitialNotification = async () => {
  const lastResponse = await Notifications.getLastNotificationResponseAsync();
  
  if (lastResponse) {
    const data = lastResponse.notification.request.content.data;
    console.log("App opened via notification tap:", data);
    
    // Perform navigation logic here
  }
};

useEffect(() => {
  handleInitialNotification();
}, []);

3. Extracting the "Hidden" Payload

Every notification can carry a data object (JSON). This is where you store the metadata needed for navigation, such as IDs, categories, or URLs.

index.tstypescript
// When scheduling:
content: {
  title: "New Message",
  data: { 
    url: "https://snabcode.com/blog/i18n",
    type: "external_link"
  }
}

// When receiving:
const url = response.notification.request.content.data.url;
if (url) {
  Linking.openURL(url);
}

Integration with Navigation

To implement professional Deep Linking, it is recommended to use a Global Navigation Ref or a dedicated hook that has access to your router (like useRouter in Expo Router).

Professional Pattern:

  1. Listen for the response at the root level.
  2. Verify the data structure.
  3. Navigate using your app's routing system.

Your Challenge

Enhance your "Daily Reflection" notification from the previous lesson. Implement the logic to detect when the user taps it and use alert() to simulate navigating them to a specialized "Reflection Screen" based on the data payload.

[!TIP] Always test the "Killed" state by manually force-closing your app, sending a scheduled notification, and then tapping it from the system tray. This is where most notification bugs hide!