Lesson 9: Action Buttons — Immediate Interactive Responses
A truly "smart" application doesn't always force the user to open it. Consider how WhatsApp allows you to "Reply" or "Mark as Read" directly from the notification tray. This feature is called Action Buttons, and it transforms your notifications from passive alerts into interactive components of the user's OS.
In this lesson, we will master:
- Notification Categories: Defining the blueprint for interactive buttons.
- Creating Custom Actions: Adding buttons like "Accept," "Reject," or "Snooze."
- Background vs. Foreground Buttons: Deciding which actions require the full app to open.
- Processing the Response: Listening for specific button identifiers.
1. What are Notification Categories?
Before you can show buttons, you must tell the operating system: "When you receive a notification of Type X, attach these specific buttons to it." These blueprints are called Categories.
Each category has a unique ID and a list of associated actions (the buttons).
2. Defining Categories in Expo
You should define your categories during the app's initialization phase, similar to how we set up channels and handlers.
index.tstypescriptimport * as Notifications from 'expo-notifications'; const defineCategories = async () => { await Notifications.setNotificationCategoryAsync('task_actions', [ { identifier: 'complete', buttonTitle: 'Mark Done ✅', options: { opensAppToForeground: false }, // Silent background execution }, { identifier: 'remind_later', buttonTitle: 'Snooze ⏰', options: { opensAppToForeground: true }, // Opens app to reschedule }, ]); };
Options:
opensAppToForeground: Iffalse, the user's current flow isn't interrupted. Your listener will execute in the background.
3. Listening for Button Taps
To detect which button was pressed, we return to our addNotificationResponseReceivedListener. However, this time we check the actionIdentifier property.
index.tstypescriptNotifications.addNotificationResponseReceivedListener(response => { const actionId = response.actionIdentifier; const { taskId } = response.notification.request.content.data; if (actionId === 'complete') { // Logic to mark the task as finished in your database console.log(`Task ${taskId} marked as complete from the tray.`); } else if (actionId === 'remind_later') { // Logic to navigate to the "Reschedule" screen navigation.navigate('Reschedule', { id: taskId }); } });
4. Sending the Trigger
To display the buttons, simply attach the categoryIdentifier to your notification content when scheduling.
index.tstypescriptawait Notifications.scheduleNotificationAsync({ content: { title: 'Pending Task 📋', body: 'Don't forget to buy groceries today.', categoryIdentifier: 'task_actions', // Matches the ID above data: { taskId: 101 }, channelId: 'tasks', }, trigger: null, });
Once this notification arrives, the user can expand it to see the "Mark Done" and "Snooze" buttons.
Technical Note: Android vs. iOS
On Android, these buttons appear directly beneath the notification body. On iOS, the user usually needs to long-press the notification to reveal the action buttons. Ensure your UX instructions (if any) account for this platform-native behavior.
Your Challenge
Create an "Invitation" category with "Accept" and "Decline" buttons. Send a test notification and implement logic to alert() a thank you message if accepted, or a log an "Unavailable" status if declined.