Push Notification Module
The Push Notification module creates a complete notification system using Firebase Cloud Messaging and Flutter Local Notifications.
Generated Structure
lib/core/notifications/
├── models/
│ └── push_notification_model.dart
├── services/
│ ├── fcm_service.dart
│ └── local_notification_service.dart
└── notification_handler.dart
Key Components
1. Notification Model (push_notification_model.dart
)
Defines the data structure for notifications:
class PushNotificationModel {
final String title;
final String body;
final String? imageUrl;
final String? payload;
PushNotificationModel({
required this.title,
required this.body,
this.imageUrl,
this.payload,
});
factory PushNotificationModel.fromJson(Map<String, dynamic> json) {
return PushNotificationModel(
title: json['title'] ?? '',
body: json['body'] ?? '',
imageUrl: json['imageUrl'],
payload: json['payload'],
);
}
Map<String, dynamic> toJson() {
return {
'title': title,
'body': body,
'imageUrl': imageUrl,
'payload': payload,
};
}
}
2. FCM Service (fcm_service.dart
)
Handles Firebase Cloud Messaging integration:
class FCMService {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
final LocalNotificationService _localNotificationService;
final List<Function(PushNotificationModel)> _onNotificationReceivedListeners = [];
FCMService(this._localNotificationService);
/// Initialize FCM service
Future<void> initialize() async {
await _requestPermissions();
await _setupForegroundNotifications();
await _setupBackgroundAndTerminatedNotifications();
await _setupOnMessageOpenedApp();
// Get FCM token
String? token = await getToken();
if (token != null) {
debugPrint('FCM Token: $token');
}
// Listen for token refreshes
_firebaseMessaging.onTokenRefresh.listen((newToken) {
debugPrint('FCM Token refreshed: $newToken');
});
}
/// Request notification permissions
Future<void> _requestPermissions() async {
NotificationSettings settings = await _firebaseMessaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
debugPrint('FCM permission status: ${settings.authorizationStatus}');
}
/// Setup handling of foreground notifications
Future<void> _setupForegroundNotifications() async {
// Handle foreground messages
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
debugPrint('Got a message whilst in the foreground!');
debugPrint('Message data: ${message.data}');
if (message.notification != null) {
debugPrint('Message also contained a notification: ${message.notification}');
final notification = PushNotificationModel(
title: message.notification?.title ?? 'New Notification',
body: message.notification?.body ?? '',
payload: json.encode(message.data),
);
// Show local notification
_localNotificationService.showNotification(notification);
// Notify listeners
_notifyListeners(notification);
}
});
}
// Other methods for handling notifications...
}
3. Local Notification Service (local_notification_service.dart
)
Manages on-device notifications:
class LocalNotificationService {
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
final List<Function(String?)> _onNotificationTapListeners = [];
/// Initialize local notification service
Future<void> initialize() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
final DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
);
}
/// Show a notification
Future<void> showNotification(PushNotificationModel notification) async {
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
);
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
await _flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
platformChannelSpecifics,
payload: notification.payload,
);
}
/// Handle notification response when tapped
void _onDidReceiveNotificationResponse(NotificationResponse response) {
final String? payload = response.payload;
// Notify listeners
for (var listener in _onNotificationTapListeners) {
listener(payload);
}
}
// Other methods...
}
4. Notification Handler (notification_handler.dart
)
Provides a unified API for the app:
class NotificationHandler {
late final FCMService _fcmService;
late final LocalNotificationService _localNotificationService;
/// Initialize notification services
Future<bool> initialize() async {
try {
// Setup local notifications first
_localNotificationService = LocalNotificationService();
await _localNotificationService.initialize();
// Setup FCM service
_fcmService = FCMService(_localNotificationService);
await _fcmService.initialize();
// Register background message handler
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
return true;
} catch (e) {
debugPrint('Error initializing notification services: $e');
return false;
}
}
/// Get FCM token
Future<String?> getFCMToken() async {
try {
return await _fcmService.getToken();
} catch (e) {
debugPrint('Error in getFCMToken: $e');
return 'Error: $e';
}
}
/// Show a local notification
Future<void> showLocalNotification(PushNotificationModel notification) async {
await _localNotificationService.showNotification(notification);
}
/// Subscribe to a FCM topic
Future<void> subscribeToTopic(String topic) async {
await _fcmService.subscribeToTopic(topic);
}
/// Unsubscribe from a FCM topic
Future<void> unsubscribeFromTopic(String topic) async {
await _fcmService.unsubscribeFromTopic(topic);
}
/// Add listener for notification received
void addOnNotificationReceivedListener(Function(PushNotificationModel) listener) {
_fcmService.addOnNotificationReceivedListener(listener);
}
/// Add listener for notification tap
void addOnNotificationTapListener(Function(String?) listener) {
_localNotificationService.addOnNotificationTapListener(listener);
}
}
/// Global notification handler instance
final notificationHandler = NotificationHandler();
5. Background Message Handler
Handles notifications when the app is in the background:
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Need to initialize Firebase before using it
await Firebase.initializeApp();
debugPrint('Handling background message: ${message.messageId}');
// Initialize FlutterLocalNotificationsPlugin and show notification
// ...
}
How to Use the Push Notification System
Initialize notifications in your main.dart
:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Initialize notification services
await notificationHandler.initialize();
runApp(const App());
}
Send a test notification:
final notification = PushNotificationModel(
title: 'Test Notification',
body: 'This is a test notification from your app',
payload: '{"type": "test", "id": "123"}',
);
await notificationHandler.showLocalNotification(notification);
Listen for notification taps:
@override
void initState() {
super.initState();
// Listen for notification taps
notificationHandler.addOnNotificationTapListener(_onNotificationTap);
}
void _onNotificationTap(String? payload) {
if (payload != null) {
try {
final data = json.decode(payload);
// Handle notification tap
} catch (e) {
debugPrint('Error parsing payload: $e');
}
}
}
@override
void dispose() {
notificationHandler.removeOnNotificationTapListener(_onNotificationTap);
super.dispose();
}
Customizing the Push Notification System
- Custom Notification Channels: Modify the Android notification channels in
local_notification_service.dart
- Custom Notification Sounds: Add sound files to your project and reference them in the notification details
- Actionable Notifications: Add buttons or quick reply options to notifications
Example of creating a notification with action buttons:
// In local_notification_service.dart
Future<void> showNotificationWithActions(PushNotificationModel notification) async {
// For Android
final androidPlatformChannelSpecifics = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
actions: <AndroidNotificationAction>[
AndroidNotificationAction(
'accept',
'Accept',
showsUserInterface: true,
),
AndroidNotificationAction(
'reject',
'Reject',
showsUserInterface: true,
),
],
);
// For iOS
final iOSPlatformChannelSpecifics = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
categoryIdentifier: 'actionableNotification',
);
final platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
await _flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
platformChannelSpecifics,
payload: notification.payload,
);
}
Common Module Integration
All these modules work together in the FlutterBunnyScreen
demonstration screen that Flutter Bunny CLI generates. This screen showcases:
- Theme switching with your chosen state management
- Language selection and switching
- Push notification testing
The screen provides a practical example of how to use all these modules together in your application, making it an excellent learning resource.