Courses/Grit Mobile/Push Notifications
Course 4 of 5~30 min10 challenges

Push Notifications

Push notifications keep users engaged by delivering timely messages even when the app is closed. In this course, you will learn how push notifications work end-to-end — from registering device tokens to sending notifications from your Go API and handling them in the app.


What are Push Notifications?

Push Notification: A message that appears on the user's phone even when the app is closed. The message shows up in the notification tray (the pull-down area on both iOS and Android). Push notifications are sent from your server through Apple (APNS) or Google (FCM) to the device.

Push notifications are different from in-app messages. An in-app message only shows when the user has the app open. A push notification reaches the user regardless — their phone buzzes, the message appears on the lock screen, and tapping it opens your app.

The flow involves three parties:

  • 1. Your app — registers for notifications and gets a push token
  • 2. Your Go API — stores the token and sends notifications when events occur
  • 3. Apple/Google — delivers the notification to the specific device
1

Challenge: Understand the Flow

Explain the push notification flow in your own words. Why can't your server send a notification directly to a phone? Why do Apple and Google sit in the middle?

Expo Push Notifications

Expo simplifies push notifications significantly. Instead of configuring APNS and FCM separately, you use Expo's unified push service. Your server sends a notification to Expo, and Expo handles routing it through APNS or FCM depending on the device.

Push Token: A unique identifier for a specific device and app combination. Your server uses this token to send a notification to that exact device. Each device has a different token, and tokens can change over time (e.g., when the user reinstalls the app).

The complete flow looks like this:

1. App launches → requests notification permissions
2. App gets Expo push token (e.g., "ExponentPushToken[xxxxxx]")
3. App sends token to your API: POST /api/devices { push_token: token }
4. API stores token in the database (linked to the user)
5. When an event occurs, API sends notification via Expo push service
6. Expo routes it through APNS (iOS) or FCM (Android)
7. Device receives the notification
2

Challenge: Request Permissions

What permissions does the app need to request for push notifications? On iOS, the user must explicitly grant permission. On Android 13+, notification permission is also required. What happens if the user denies permission?

Getting the Push Token

Use Expo's Notifications API to get the device's push token:

import * as Notifications from 'expo-notifications'
import * as Device from 'expo-device'
import { Platform } from 'react-native'

async function registerForPushNotifications() {
  // Push notifications only work on physical devices
  if (!Device.isDevice) {
    console.log('Push notifications require a physical device')
    return null
  }

  // Request permission
  const { status: existingStatus } = await Notifications.getPermissionsAsync()
  let finalStatus = existingStatus

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

  if (finalStatus !== 'granted') {
    console.log('Permission not granted for push notifications')
    return null
  }

  // Get the Expo push token
  const token = (await Notifications.getExpoPushTokenAsync()).data
  console.log('Push token:', token)

  // Android needs a notification channel
  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
    })
  }

  return token
}

After getting the token, send it to your API so the server can store it:

// Send token to your API
const token = await registerForPushNotifications()
if (token) {
  await api.post('/api/devices', { push_token: token })
}
Push notifications do not work in the iOS Simulator or Android Emulator. You need a physical device to test them. The token registration code should handle this gracefully by checking Device.isDevice first.
3

Challenge: Register a Push Token

On a physical device, run the push token registration code. What does the token look like? It should start with "ExponentPushToken[". Send it to your API and verify it's stored in the database.

The grit-notifications Plugin

Grit provides a notifications plugin that handles device token storage, sending notifications to one or many devices, and keeping a notification history.

go get github.com/MUKE-coder/grit-plugins/grit-notifications

The plugin provides:

  • Device model — stores push tokens linked to user accounts
  • Send to one — send a notification to a single device
  • Send to many — broadcast to all devices of a user, or all users
  • Notification history — log of all sent notifications for debugging
  • Token cleanup — automatically removes invalid tokens
4

Challenge: Explore the Plugin

Look at the grit-notifications plugin documentation. What functions does it expose? What database tables does it create?

Sending a Notification

From your Go API, send a notification using the plugin:

import notify "github.com/MUKE-coder/grit-plugins/grit-notifications"

// Send a notification to a specific device
err := notify.Send(notify.PushMessage{
    To:    pushToken,
    Title: "New Workout Added",
    Body:  "Your trainer added a new workout for today.",
    Data:  map[string]string{"screen": "workout", "id": "42"},
})

// Send to all devices of a specific user
err = notify.SendToUser(db, userID, notify.PushMessage{
    Title: "Weekly Summary",
    Body:  "You completed 5 workouts this week!",
    Data:  map[string]string{"screen": "stats"},
})

The Data field is a key-value map that is delivered to the app but not shown to the user. Use it to pass information the app needs to handle the notification — like which screen to navigate to and what ID to load.

Always include a screen key in the Data field. This tells the app which screen to navigate to when the user taps the notification.
5

Challenge: Design a Notification

If you were building an order confirmation notification for an e-commerce app, what would the Title, Body, and Data fields contain? Write out the complete PushMessage struct.

Handling Notifications

Notifications can arrive in three different states, and you need to handle each one:

  • Foreground — app is open and visible. Show an in-app banner or toast.
  • Background — app is in the background. The OS shows the notification in the tray.
  • Killed — app is completely closed. The OS shows the notification. Tapping it opens the app.
import * as Notifications from 'expo-notifications'
import { useRouter } from 'expo-router'

export function useNotificationListeners() {
  const router = useRouter()

  useEffect(() => {
    // Foreground: notification received while app is open
    const receivedSub = Notifications.addNotificationReceivedListener(
      (notification) => {
        const data = notification.request.content.data
        console.log('Received in foreground:', data)
        // Show an in-app toast or banner
      }
    )

    // User tapped: notification was tapped (from any state)
    const responseSub = Notifications.addNotificationResponseReceivedListener(
      (response) => {
        const data = response.notification.request.content.data
        // Navigate to the relevant screen
        if (data.screen === 'workout' && data.id) {
          router.push('/workouts/' + data.id)
        }
      }
    )

    return () => {
      receivedSub.remove()
      responseSub.remove()
    }
  }, [])
}
Notification Channel (Android): On Android 8+, notifications must belong to a channel. Channels let users control notification settings per category — they can mute "Marketing" notifications while keeping "Order Updates" enabled. You create channels in your app code.
6

Challenge: Handle Notification Tap

What should happen when a user taps a notification about a new workout? The app should open, navigate to the workout detail screen, and show the specific workout. Write the handler code.

7

Challenge: Foreground Behavior

When a notification arrives while the app is open (foreground), the default behavior on iOS is to not show it. How would you configure Expo to show a banner even in the foreground? (Hint: look at setNotificationHandler)

Local Notifications

Local Notification: A notification triggered by the app itself — not from a server. Local notifications are useful for reminders, timers, alarms, and scheduled prompts. They work offline and do not require a push token or server infrastructure.

Use Expo's scheduleNotificationAsync to schedule a local notification:

import * as Notifications from 'expo-notifications'

// Schedule a notification for 1 hour from now
await Notifications.scheduleNotificationAsync({
  content: {
    title: "Reminder",
    body: "Time to work out!",
    data: { screen: "workouts" },
  },
  trigger: {
    seconds: 3600, // 1 hour
  },
})

// Schedule a daily notification at 9:00 AM
await Notifications.scheduleNotificationAsync({
  content: {
    title: "Good morning!",
    body: "Ready for today's workout?",
  },
  trigger: {
    hour: 9,
    minute: 0,
    repeats: true,
  },
})

Local notifications are perfect for features like:

  • Workout reminders ("Time to exercise!")
  • Timer completions ("Rest period over — next set!")
  • Daily streaks ("Don't break your 7-day streak!")
  • Meal reminders ("Time to log your lunch")
8

Challenge: Schedule a Local Notification

Schedule a local notification that fires 10 seconds from now with a title and body of your choice. Does it appear on your device? What happens if you close the app before it fires?

Summary

Here's what you learned in this course:

  • Push notifications are delivered through Apple (APNS) or Google (FCM)
  • Expo simplifies push by providing a unified API for both platforms
  • Push tokens uniquely identify a device and must be stored on your server
  • Handle three states: foreground, background, and killed
  • Local notifications work offline and are great for reminders and timers
9

Challenge: Design a Notification System

When should your fitness app send push notifications? List 5 scenarios. For each, specify whether it should be a push notification (from the server) or a local notification (from the app).

10

Challenge: Write the Payloads

For each of your 5 scenarios from the previous challenge, write the complete notification payload: Title, Body, and Data (with screen and ID). Make the copy concise and actionable.