swift_notifications 0.0.1
swift_notifications: ^0.0.1 copied to clipboard
A unified Flutter plugin for rich push and local notifications on Android, iOS, and macOS with no native code required.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:swift_notifications/swift_notifications.dart';
import 'dart:async';
import 'dart:convert';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Enable verbose logging for debugging
SwiftNotifications.enableVerboseLogging();
// Initialize notifications
final notifications = SwiftNotifications();
await notifications.initialize(verbose: true);
runApp(MyApp(notifications: notifications));
}
class MyApp extends StatelessWidget {
final SwiftNotifications notifications;
const MyApp({super.key, required this.notifications});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Swift Notifications Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
routes: {
'/': (context) => HomeScreen(notifications: notifications),
'/message': (context) => MessageScreen(),
'/order': (context) => OrderScreen(),
'/settings': (context) => SettingsScreen(notifications: notifications),
'/test-cold-start': (context) => ColdStartTestScreen(notifications: notifications),
},
initialRoute: '/',
);
}
}
class HomeScreen extends StatefulWidget {
final SwiftNotifications notifications;
const HomeScreen({super.key, required this.notifications});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _status = 'Initializing...';
NotificationPermissionStatus _permissionStatus = NotificationPermissionStatus.notDetermined;
StreamSubscription<NotificationResponse>? _notificationSubscription;
List<NotificationResponse> _notificationHistory = [];
@override
void initState() {
super.initState();
_checkPermission();
_listenToNotifications();
}
Future<void> _checkPermission() async {
try {
final status = await widget.notifications.checkPermission();
setState(() {
_permissionStatus = status;
_status = 'Permission: ${status.name}';
});
} catch (e) {
setState(() {
_status = 'Error: $e';
});
}
}
Future<void> _requestPermission() async {
try {
final status = await widget.notifications.requestPermission();
setState(() {
_permissionStatus = status;
_status = 'Permission: ${status.name}';
});
_showSnackBar('Permission status: ${status.name}');
} catch (e) {
_showSnackBar('Error requesting permission: $e');
}
}
void _listenToNotifications() {
_notificationSubscription = widget.notifications.onNotificationResponse.listen((response) {
setState(() {
_notificationHistory.insert(0, response);
if (_notificationHistory.length > 10) {
_notificationHistory.removeLast();
}
});
_showNotificationDialog(response);
});
}
void _showNotificationDialog(NotificationResponse response) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Notification Response'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('Notification ID: ${response.notificationId}'),
const SizedBox(height: 8),
Text('Action ID: ${response.actionId ?? "Tapped (body)"}'),
if (response.payload != null) ...[
const SizedBox(height: 8),
const Text('Payload:', style: TextStyle(fontWeight: FontWeight.bold)),
Text(JsonEncoder.withIndent(' ').convert(response.payload!)),
],
if (response.actionPayload != null) ...[
const SizedBox(height: 8),
const Text('Action Payload:', style: TextStyle(fontWeight: FontWeight.bold)),
Text(JsonEncoder.withIndent(' ').convert(response.actionPayload!)),
],
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
),
);
}
@override
void dispose() {
_notificationSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Swift Notifications'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () => Navigator.pushNamed(context, '/settings'),
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Status Card
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text(
'Status',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(_status),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _checkPermission,
child: const Text('Check Permission'),
),
ElevatedButton(
onPressed: _requestPermission,
child: const Text('Request Permission'),
),
],
),
],
),
),
),
const SizedBox(height: 16),
// Basic Notifications Section
_buildSection(
title: 'Basic Notifications',
children: [
_buildButton(
'Simple Notification',
() => _showSimpleNotification(),
),
_buildButton(
'Notification with Payload',
() => _showNotificationWithPayload(),
),
_buildButton(
'Notification for Cold Start Test',
() => _showColdStartNotification(),
),
],
),
// Rich Notifications Section
_buildSection(
title: 'Rich Notifications',
children: [
_buildButton(
'Notification with Image',
() => _showImageNotification(),
),
_buildButton(
'Notification with Buttons',
() => _showNotificationWithButtons(),
),
_buildButton(
'Rich Notification (Image + Buttons)',
() => _showRichNotification(),
),
],
),
// Advanced Features Section
_buildSection(
title: 'Advanced Features',
children: [
_buildButton(
'Test Cold Start Handling',
() => Navigator.pushNamed(context, '/test-cold-start'),
),
_buildButton(
'Notification with Custom Category',
() => _showNotificationWithCategory(),
),
_buildButton(
'Notification with Priority',
() => _showNotificationWithPriority(),
),
],
),
// Actions Section
_buildSection(
title: 'Actions',
children: [
_buildButton(
'Cancel All Notifications',
() => _cancelAllNotifications(),
color: Colors.red,
),
],
),
// Notification History
if (_notificationHistory.isNotEmpty) ...[
const SizedBox(height: 24),
const Text(
'Notification History',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
..._notificationHistory.map((response) => Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text('ID: ${response.notificationId}'),
subtitle: Text('Action: ${response.actionId ?? "Tapped"}'),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () => _showNotificationDialog(response),
),
)),
],
],
),
),
);
}
Widget _buildSection({required String title, required List<Widget> children}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
...children,
const SizedBox(height: 24),
],
);
}
Widget _buildButton(String text, VoidCallback onPressed, {Color? color}) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ElevatedButton(
onPressed: onPressed,
style: color != null ? ElevatedButton.styleFrom(backgroundColor: color) : null,
child: Text(text),
),
);
}
Future<void> _showSimpleNotification() async {
try {
await widget.notifications.showSimpleNotification(
id: 'simple_${DateTime.now().millisecondsSinceEpoch}',
title: 'Simple Notification',
body: 'This is a simple text notification without any extras.',
);
_showSnackBar('Simple notification sent!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _showNotificationWithPayload() async {
try {
await widget.notifications.showSimpleNotification(
id: 'payload_${DateTime.now().millisecondsSinceEpoch}',
title: 'Notification with Payload',
body: 'Tap this notification to see the payload data.',
payload: {
'type': 'user_message',
'userId': '12345',
'message': 'Hello from notification!',
'timestamp': DateTime.now().toIso8601String(),
},
);
_showSnackBar('Notification with payload sent!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _showColdStartNotification() async {
try {
await widget.notifications.showNotification(
NotificationRequest(
id: 'coldstart_${DateTime.now().millisecondsSinceEpoch}',
title: 'Cold Start Test',
body: 'Close the app completely, then tap this notification. The event will be stored and delivered when the app starts.',
payload: {
'type': 'cold_start_test',
'message': 'This notification tests cold start event handling',
'timestamp': DateTime.now().toIso8601String(),
},
buttonsEnabled: true,
actions: [
NotificationAction(
id: 'view_message',
title: 'View Message',
payload: {'action': 'view', 'screen': '/message'},
),
NotificationAction(
id: 'view_order',
title: 'View Order',
payload: {'action': 'view', 'screen': '/order'},
),
],
),
);
_showSnackBar('Cold start test notification sent! Close the app and tap it.');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _showImageNotification() async {
try {
await widget.notifications.showImageNotification(
id: 'image_${DateTime.now().millisecondsSinceEpoch}',
title: 'Image Notification',
body: 'This notification includes an image!',
imageUrl: 'https://picsum.photos/400/300?random=${DateTime.now().millisecondsSinceEpoch}',
);
_showSnackBar('Image notification sent!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _showNotificationWithButtons() async {
try {
await widget.notifications.showNotification(
NotificationRequest(
id: 'buttons_${DateTime.now().millisecondsSinceEpoch}',
title: 'Notification with Buttons',
body: 'This notification has action buttons. Try tapping them!',
buttonsEnabled: true,
actions: [
NotificationAction(
id: 'action_view',
title: 'View',
payload: {'action': 'view'},
),
NotificationAction(
id: 'action_reply',
title: 'Reply',
payload: {'action': 'reply'},
),
NotificationAction(
id: 'action_delete',
title: 'Delete',
isDestructive: true,
payload: {'action': 'delete'},
),
],
),
);
_showSnackBar('Notification with buttons sent!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _showRichNotification() async {
try {
await widget.notifications.showNotification(
NotificationRequest(
id: 'rich_${DateTime.now().millisecondsSinceEpoch}',
title: 'Rich Notification',
body: 'This is a rich notification with both image and buttons!',
imageEnabled: true,
image: NotificationImage(
url: 'https://picsum.photos/400/300?random=${DateTime.now().millisecondsSinceEpoch}',
),
buttonsEnabled: true,
actions: [
NotificationAction(
id: 'action_like',
title: '👍 Like',
payload: {'action': 'like'},
),
NotificationAction(
id: 'action_share',
title: '📤 Share',
payload: {'action': 'share'},
),
],
payload: {
'type': 'rich_notification',
'timestamp': DateTime.now().toIso8601String(),
},
),
);
_showSnackBar('Rich notification sent!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _showNotificationWithCategory() async {
try {
await widget.notifications.showNotification(
NotificationRequest(
id: 'category_${DateTime.now().millisecondsSinceEpoch}',
title: 'Categorized Notification',
body: 'This notification uses a custom category.',
categoryId: 'custom_category',
payload: {'category': 'custom_category'},
),
);
_showSnackBar('Categorized notification sent!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _showNotificationWithPriority() async {
try {
await widget.notifications.showNotification(
NotificationRequest(
id: 'priority_${DateTime.now().millisecondsSinceEpoch}',
title: 'High Priority Notification',
body: 'This notification has high priority (Android).',
priority: NotificationPriority.high,
payload: {'priority': 'high'},
),
);
_showSnackBar('High priority notification sent!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
Future<void> _cancelAllNotifications() async {
try {
await widget.notifications.cancelAllNotifications();
_showSnackBar('All notifications cancelled!');
} catch (e) {
_showSnackBar('Error: $e');
}
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
}
// Example screens for routing
class MessageScreen extends StatelessWidget {
const MessageScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Message Screen'),
),
body: const Center(
child: Text('This is the message screen'),
),
);
}
}
class OrderScreen extends StatelessWidget {
const OrderScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Order Screen'),
),
body: const Center(
child: Text('This is the order screen'),
),
);
}
}
class SettingsScreen extends StatefulWidget {
final SwiftNotifications notifications;
const SettingsScreen({super.key, required this.notifications});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool _verboseLogging = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
title: const Text('Verbose Logging'),
subtitle: const Text('Enable detailed logging for debugging'),
value: _verboseLogging,
onChanged: (value) {
setState(() {
_verboseLogging = value;
if (value) {
SwiftNotifications.enableVerboseLogging();
} else {
SwiftNotifications.disableVerboseLogging();
}
});
},
),
const Divider(),
ListTile(
title: const Text('About'),
subtitle: const Text('Swift Notifications Example App'),
),
],
),
);
}
}
class ColdStartTestScreen extends StatelessWidget {
final SwiftNotifications notifications;
const ColdStartTestScreen({super.key, required this.notifications});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Cold Start Test'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Card(
color: Colors.blue,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'How to Test Cold Start',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
),
SizedBox(height: 8),
Text(
'1. Send a notification using the button below\n'
'2. Close the app completely (swipe away from recent apps)\n'
'3. Tap the notification\n'
'4. The app will launch and the event will be delivered',
style: TextStyle(color: Colors.white),
),
],
),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
try {
await notifications.showNotification(
NotificationRequest(
id: 'coldstart_test_${DateTime.now().millisecondsSinceEpoch}',
title: 'Cold Start Test',
body: 'Close the app completely, then tap this notification to test cold start handling.',
payload: {
'type': 'cold_start_test',
'testId': DateTime.now().millisecondsSinceEpoch.toString(),
'message': 'This is a cold start test notification',
},
buttonsEnabled: true,
actions: [
NotificationAction(
id: 'test_action_1',
title: 'Test Action 1',
payload: {'action': 'test1', 'screen': '/message'},
),
NotificationAction(
id: 'test_action_2',
title: 'Test Action 2',
payload: {'action': 'test2', 'screen': '/order'},
),
],
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Notification sent! Close the app and tap it to test cold start.'),
duration: Duration(seconds: 5),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
},
child: const Text('Send Cold Start Test Notification'),
),
const SizedBox(height: 16),
const Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'What Happens:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text('• Notification event is stored in persistent storage'),
Text('• App launches from cold start'),
Text('• Plugin checks for pending events on initialization'),
Text('• Event is delivered to onNotificationResponse stream'),
Text('• You can handle routing based on the response'),
],
),
),
),
],
),
),
);
}
}