When building mobile applications, how your app responds to a push notification depends critically on its current state: foreground, background, or killed. Understanding these states is crucial for delivering a seamless user experience. In the foreground state, your app is actively open and visible to the user. When a notification arrives in this state, the operating system typically does not display its default visual alert (like a banner or sound). Instead, your application takes full control. This allows you to process the notification data silently, update UI elements, or present a custom, in-app notification experience (like a snackbar or a small banner within your app's existing layout) without interrupting the user's flow with a system-level alert. This direct control is key for subtle and context-aware interactions.
Next is the background state, where your app is minimized, suspended, or running but not actively visible on the screen. In this scenario, the operating system usually takes over the responsibility of displaying the notification to the user, typically in the notification shade, as a banner, or with a badge icon. However, your app can still receive and process the notification's data payload. For instance, you might update cached data, perform a quick sync, or prepare UI elements for when the user brings the app back to the foreground. If the user taps on the system-displayed notification, your app will be brought to the foreground, and the notification's payload will often be passed along, enabling you to deep link to specific content within the app.
Finally, the killed-state (also known as not-running or terminated state) occurs when your app is completely closed, either by the user force-quitting it or by the system to free up resources. When a push notification arrives for an app in this state, your app's code is not running to intercept it immediately. The operating system handles displaying the notification entirely. The critical difference here is that if the user taps on this notification, the app will be launched from scratch. Upon launch, the app can then access the notification's payload, allowing you to direct the user to a specific screen or initiate an action based on the notification content, effectively deep linking from a cold start. Consistent handling across all states ensures users can always access relevant content directly from a notification.
Key Takeaways
- Notification handling logic varies significantly based on the app's current state: foreground, background, or killed.
- In the foreground, your app has full control and typically presents custom in-app UI rather than system alerts.
- In the background, the OS displays the notification, but your app can still process data and respond to taps.
- In the killed-state, the OS handles display, and the app is launched from scratch upon tap, accessing the payload post-launch.
- Implementing deep linking is crucial across all states for a consistent user experience, directing users to specific app content.
Code Example
import 'package:firebase_messaging/firebase_messaging.dart';
// This handler is registered typically in your main.dart or a similar setup file
void setupFirebaseMessaging() {
// Handle messages when the app is in the foreground
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Foreground message received!');
print('Notification title: ${message.notification?.title}');
print('Notification body: ${message.notification?.body}');
print('Data payload: ${message.data}');
// You can now display a custom in-app UI (e.g., a snackbar, dialog)
// or process the data silently without interrupting the user.
// Example: showToast(message.notification?.body ?? 'New notification!');
});
}How this code works
This code snippet's primary job is to manage push notifications specifically when the application is actively running and visible on the user's screen – a state known as the "foreground." Instead of relying on the device's operating system to automatically display a standard notification alert, this setup empowers the app to completely take over. When a push notification arrives while the app is in the foreground, this code intercepts it, allowing the application to decide precisely how to present the information to the user, perhaps with a custom in-app UI, or to silently process any underlying data without interruption.
The setupFirebaseMessaging function initiates this listening process by calling FirebaseMessaging.onMessage.listen. This listen method effectively sets up a continuous watch; whenever a new RemoteMessage arrives while the app is in the foreground, the code defined within the callback runs. Inside this callback, the message object provides all the notification's details, letting the code extract visible text like message.notification?.title and message.notification?.body, or access custom information sent from the server via message.data. A subtle but crucial point for beginners is that FirebaseMessaging.onMessage only handles messages when the app is in the foreground; for notifications received in background or killed states, different Firebase Messaging handlers are needed, and the system typically handles the initial display.