swift_notifications 0.0.1 copy "swift_notifications: ^0.0.1" to clipboard
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.

Swift Notifications #

One API. Three Platforms. Zero Native Setup.

A unified Flutter plugin for rich push and local notifications on Android, iOS, and macOS with no native code required.

🌟 Features #

  • Unified API - Same code works on Android, iOS, and macOS
  • Rich Notifications - Support for images and action buttons
  • Zero Native Setup - No manifest edits, no plist configuration, no native code
  • Automatic Setup - Permissions, channels, categories handled automatically
  • Works Everywhere - Foreground, background, and terminated app states
  • Graceful Fallbacks - Falls back to text notifications if images fail

📦 Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  swift_notifications: ^0.0.1

Then run:

flutter pub get

🚀 Quick Start #

1. Initialize the Plugin #

import 'package:swift_notifications/swift_notifications.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  final notifications = SwiftNotifications();
  await notifications.initialize();
  await notifications.requestPermission();
  
  runApp(MyApp());
}

2. Show a Simple Notification #

await notifications.showSimpleNotification(
  id: 'notification_1',
  title: 'Hello!',
  body: 'This is a simple notification',
);

3. Show a Rich Notification with Image #

await notifications.showImageNotification(
  id: 'notification_2',
  title: 'Check this out!',
  body: 'This notification has an image',
  imageUrl: 'https://example.com/image.jpg',
);

4. Show a Notification with Buttons #

await notifications.showNotification(
  NotificationRequest(
    id: 'notification_3',
    title: 'New Message',
    body: 'You have a new message',
    buttonsEnabled: true,
    actions: [
      NotificationAction(
        id: 'reply',
        title: 'Reply',
        payload: {'action': 'reply'},
      ),
      NotificationAction(
        id: 'delete',
        title: 'Delete',
        isDestructive: true,
        payload: {'action': 'delete'},
      ),
    ],
  ),
);

5. Handle Notification Responses #

notifications.onNotificationResponse.listen((response) {
  print('Notification ID: ${response.notificationId}');
  print('Action ID: ${response.actionId}');
  print('Payload: ${response.payload}');
  
  if (response.actionId == 'reply') {
    // Handle reply action
  }
});

📚 API Reference #

SwiftNotifications #

Main class for managing notifications.

Methods

initialize()

Initialize the notification plugin. Call this once when your app starts.

await notifications.initialize();
requestPermission()

Request notification permissions from the user.

final status = await notifications.requestPermission();
// Returns: NotificationPermissionStatus.granted, .denied, etc.
checkPermission()

Check the current permission status without requesting.

final status = await notifications.checkPermission();
showNotification(NotificationRequest request)

Show a notification with full customization.

await notifications.showNotification(
  NotificationRequest(
    id: 'unique_id',
    title: 'Title',
    body: 'Body',
    // ... see NotificationRequest for all options
  ),
);
showSimpleNotification({required String id, required String title, required String body, Map<String, dynamic>? payload})

Convenience method for simple text notifications.

await notifications.showSimpleNotification(
  id: 'simple_1',
  title: 'Hello',
  body: 'World',
  payload: {'key': 'value'},
);
showImageNotification({required String id, required String title, required String body, required String imageUrl, Map<String, dynamic>? payload})

Convenience method for notifications with images.

await notifications.showImageNotification(
  id: 'image_1',
  title: 'Image',
  body: 'Notification',
  imageUrl: 'https://example.com/image.jpg',
);
cancelNotification(String notificationId)

Cancel a specific notification.

await notifications.cancelNotification('notification_1');
cancelAllNotifications()

Cancel all notifications.

await notifications.cancelAllNotifications();

Streams

onNotificationResponse

Stream of notification responses (taps and button clicks).

notifications.onNotificationResponse.listen((response) {
  // Handle notification response
});

NotificationRequest #

Model for notification configuration.

NotificationRequest(
  id: 'unique_id',                    // Required: Unique identifier
  title: 'Title',                     // Required: Notification title
  body: 'Body',                       // Required: Notification body
  image: NotificationImage(...),      // Optional: Image to display
  imageEnabled: true,                 // Optional: Enable image feature
  actions: [NotificationAction(...)], // Optional: Action buttons
  buttonsEnabled: true,               // Optional: Enable buttons feature
  categoryId: 'custom_category',      // Optional: Category identifier
  payload: {'key': 'value'},          // Optional: Custom payload
  sound: 'default',                   // Optional: Sound name
  badge: 1,                           // Optional: Badge number (iOS/macOS)
  priority: NotificationPriority.high, // Optional: Priority (Android)
  showWhenForeground: true,           // Optional: Show when app is in foreground
)

NotificationAction #

Model for notification action buttons.

NotificationAction(
  id: 'action_id',                    // Required: Unique action identifier
  title: 'Action Title',              // Required: Button label
  payload: {'key': 'value'},         // Optional: Action payload
  requiresAuthentication: false,      // Optional: Require authentication
  isDestructive: false,               // Optional: Mark as destructive (red on iOS)
)

NotificationImage #

Model for notification images.

NotificationImage(
  url: 'https://example.com/image.jpg', // Required: Image URL
  localPath: '/path/to/image.jpg',     // Optional: Local file path
  downloadFromUrl: true,                // Optional: Download from URL
)

NotificationResponse #

Model for notification responses.

NotificationResponse(
  notificationId: 'notification_1',     // The notification that was tapped
  actionId: 'reply',                   // The action button clicked (null if body tapped)
  payload: {'key': 'value'},          // Notification payload
  actionPayload: {'action': 'reply'}, // Action payload (if action clicked)
)

🎯 Platform Support #

Feature Android iOS macOS
Basic Notifications
Images
Buttons
Categories
Permissions
Foreground Display
Background Display
Terminated Display

🔧 Platform-Specific Notes #

Android #

  • Minimum SDK: 24 (Android 7.0)
  • Permissions: Automatically handled. POST_NOTIFICATIONS permission is declared in the plugin.
  • Channels: Automatically created. Default channel ID: swift_notifications_default
  • Images: Downloaded and displayed using BigPictureStyle
  • Buttons: Implemented using NotificationCompat actions

iOS #

  • Minimum Version: iOS 10.0
  • Permissions: Requested automatically via UNUserNotificationCenter
  • Categories: Automatically created and registered
  • Images: Downloaded and attached as UNNotificationAttachment
  • Buttons: Implemented using UNNotificationAction

macOS #

  • Minimum Version: macOS 10.14
  • Permissions: Requested automatically via UNUserNotificationCenter
  • Categories: Automatically created and registered
  • Images: Downloaded and attached as UNNotificationAttachment
  • Buttons: Implemented using UNNotificationAction

📡 Remote Push Notifications #

Android (Firebase Cloud Messaging) #

To use remote push notifications with Firebase, you need to create your own Firebase Messaging Service. The plugin provides a helper class NotificationBuilder to make this easy:

  1. Add Firebase to your app:

    dependencies:
      firebase_core: ^2.24.0
      firebase_messaging: ^14.7.0
    
  2. Add Firebase Messaging dependency in android/app/build.gradle:

    dependencies {
        implementation 'com.google.firebase:firebase-messaging:23.4.0'
    }
    
  3. Create your Firebase Messaging Service using the helper class:

    Create android/app/src/main/kotlin/com/yourapp/YourFirebaseMessagingService.kt:

    package com.yourapp
       
    import android.util.Log
    import com.google.firebase.messaging.FirebaseMessagingService
    import com.google.firebase.messaging.RemoteMessage
    import com.swiftflutter.swift_notifications.NotificationBuilder
       
    class YourFirebaseMessagingService : FirebaseMessagingService() {
        override fun onMessageReceived(remoteMessage: RemoteMessage) {
            Log.d("FCM", "Received message: ${remoteMessage.messageId}")
               
            val data = remoteMessage.data
            val notification = remoteMessage.notification
               
            // Extract notification data from Firebase payload
            val id = data["id"] ?: remoteMessage.messageId ?: System.currentTimeMillis().toString()
            val title = data["title"] ?: notification?.title ?: "Notification"
            val body = data["body"] ?: notification?.body ?: ""
            val imageUrl = data["image"]
            val imageEnabled = data["imageEnabled"]?.toBoolean() ?: !imageUrl.isNullOrEmpty()
            val buttonsJson = data["buttons"]
            val buttonsEnabled = data["buttonsEnabled"]?.toBoolean() ?: false
            val categoryId = data["categoryId"]
            val payload = data["payload"]
            val priority = data["priority"] ?: "defaultPriority"
               
            // Use the plugin's helper to show rich notification
            NotificationBuilder.showRichNotification(
                context = applicationContext,
                id = id,
                title = title,
                body = body,
                imageUrl = imageUrl,
                imageEnabled = imageEnabled,
                buttonsJson = buttonsJson,
                buttonsEnabled = buttonsEnabled,
                categoryId = categoryId,
                payload = payload,
                priority = priority
            )
        }
           
        override fun onNewToken(token: String) {
            Log.d("FCM", "New token: $token")
            // Send token to your server if needed
        }
    }
    
  4. Register your service in android/app/src/main/AndroidManifest.xml:

    <service
        android:name=".YourFirebaseMessagingService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>
    
  5. Send notifications using the unified payload format in your Firebase data payload:

    {
      "id": "notification_123",
      "title": "New Message",
      "body": "You have a new message",
      "image": "https://example.com/image.jpg",
      "imageEnabled": "true",
      "buttons": "[{\"id\":\"reply\",\"title\":\"Reply\"},{\"id\":\"delete\",\"title\":\"Delete\",\"isDestructive\":true}]",
      "buttonsEnabled": "true",
      "payload": "{\"type\":\"message\",\"userId\":\"123\"}",
      "priority": "high"
    }
    

That's it! The NotificationBuilder helper class handles:

  • ✅ Creating notification channels
  • ✅ Downloading and attaching images
  • ✅ Creating action buttons
  • ✅ Setting up tap and action receivers
  • ✅ All the rich notification features
  • Works in background - Firebase service runs in background when app is closed

Important Notes:

  • Background Support: Yes! NotificationBuilder works when notifications are received in the background. The Firebase Messaging Service runs automatically when a push notification arrives, even if the app is closed.
  • Foreground Support: Also works when app is in foreground.
  • Terminated App: Works when app is completely closed (killed).

Your Firebase service just needs to extract the data and call NotificationBuilder.showRichNotification().

iOS (APNs with Notification Service Extension) #

For iOS rich push notifications with images, you need to set up a Notification Service Extension (NSE). The plugin provides a NotificationBuilder helper class for iOS as well.

Important Notes:

  • Background Support: Yes! Works when notifications are received in background
  • Foreground Support: Works when app is in foreground
  • Terminated App: Works when app is completely closed
  • ⚠️ NSE Required: For images in remote push, you MUST use a Notification Service Extension (NSE). This is an iOS requirement.

This is required if you want images in remote push notifications.

Step 1: Create Notification Service Extension Target

  1. Open your project in Xcode
  2. Go to File → New → Target
  3. Select Notification Service Extension
  4. Name it (e.g., "NotificationServiceExtension")
  5. Click Finish and Activate the scheme

Step 2: Update Extension Code

Replace the content of NotificationServiceExtension/NotificationService.swift:

import UserNotifications
import swift_notifications

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        guard let bestAttemptContent = bestAttemptContent else {
            contentHandler(request.content)
            return
        }
        
        // Extract data from notification payload
        let userInfo = bestAttemptContent.userInfo
        let imageUrl = userInfo["image"] as? String
        let imageEnabled = (userInfo["imageEnabled"] as? String) == "true" || imageUrl != nil
        let buttonsJson = userInfo["buttons"] as? String
        let buttonsEnabled = (userInfo["buttonsEnabled"] as? String) == "true"
        let categoryId = userInfo["categoryId"] as? String
        
        // Setup category with buttons if needed
        if buttonsEnabled {
            NotificationBuilder.setupCategory(
                categoryId: categoryId,
                buttonsJson: buttonsJson,
                buttonsEnabled: buttonsEnabled
            )
            bestAttemptContent.categoryIdentifier = categoryId ?? "swift_notifications_default"
        }
        
        // Download and attach image if enabled
        if imageEnabled, let imageUrl = imageUrl, let url = URL(string: imageUrl) {
            NotificationBuilder.downloadAndAttachImage(url: url, content: bestAttemptContent) { success in
                if !success {
                    print("Warning: Failed to load image")
                }
                contentHandler(bestAttemptContent)
            }
        } else {
            contentHandler(bestAttemptContent)
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

Step 3: Configure App Group (Optional, for sharing data)

If you need to share data between app and extension:

  1. Enable App Groups in both app and extension targets
  2. Use the same group identifier (e.g., group.com.yourapp.notifications)

Step 4: Send Remote Push with Image

When sending remote push from your server, include the image URL in the payload:

{
  "aps": {
    "alert": {
      "title": "New Message",
      "body": "You have a new message"
    },
    "mutable-content": 1
  },
  "image": "https://example.com/image.jpg",
  "id": "notification_123",
  "payload": {
    "type": "message",
    "userId": "123"
  }
}

Important: The mutable-content: 1 key is required for NSE to process the notification.

Option 2: Using AppDelegate (For Basic Notifications Without Images)

If you don't need images in remote push, you can handle notifications in your AppDelegate:

import UserNotifications
import swift_notifications

extension AppDelegate: UNUserNotificationCenterDelegate {
    // Handle notification when app is in foreground
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // Setup category with buttons if needed
        let userInfo = notification.request.content.userInfo
        let buttonsJson = userInfo["buttons"] as? String
        let buttonsEnabled = (userInfo["buttonsEnabled"] as? String) == "true"
        let categoryId = userInfo["categoryId"] as? String
        
        if buttonsEnabled {
            NotificationBuilder.setupCategory(
                categoryId: categoryId,
                buttonsJson: buttonsJson,
                buttonsEnabled: buttonsEnabled
            )
        }
        
        completionHandler([.banner, .sound, .badge])
    }
    
    // Handle notification tap
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        // Handle tap/action - events are automatically handled by the plugin
        completionHandler()
    }
}

Note: AppDelegate approach works for basic notifications, but for images in remote push, you MUST use NSE (Option 1).

macOS (APNs) #

macOS remote push works similarly to iOS but doesn't require NSE. You can handle remote push in your AppDelegate.swift:

import UserNotifications

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // Handle notification when app is in foreground
        completionHandler([.banner, .sound, .badge])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        // Handle notification tap
        let userInfo = response.notification.request.content.userInfo
        // Process userInfo and route to appropriate screen
        completionHandler()
    }
}

Note: For macOS, images in remote push can be attached directly in the delegate methods without requiring an extension.

🧊 Cold-Start Event Handling #

The plugin automatically handles notification taps and button clicks even when the app is terminated (cold start):

  • Events are stored in persistent storage when app is not running
  • Events are delivered when the app starts and Flutter is ready
  • Works seamlessly - no additional code required

Just listen to the onNotificationResponse stream as usual:

notifications.onNotificationResponse.listen((response) {
  // This will receive events even from cold start
  print('Notification: ${response.notificationId}');
  print('Action: ${response.actionId}');
});

🎯 Screen Routing with screen_launch_by_notfication #

For advanced screen routing and navigation when notifications are tapped (especially from cold start), integrate with the screen_launch_by_notfication plugin.

Why Use screen_launch_by_notfication? #

  • Automatic routing: Skip splash screens and route directly to notification-specific screens
  • Deep link support: Handle custom URL schemes and universal links
  • Cold start handling: Works when app is killed, in background, or foreground
  • Zero native setup: All native code handled automatically

Integration Example #

import 'package:swift_notifications/swift_notifications.dart';
import 'package:screen_launch_by_notfication/screen_launch_by_notfication.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  final notifications = SwiftNotifications();
  await notifications.initialize();
  await notifications.requestPermission();
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SwiftFlutterMaterial(
      materialApp: MaterialApp(
        title: 'My App',
        routes: {
          '/home': (context) => HomeScreen(),
          '/message': (context) => MessageScreen(),
          '/order': (context) => OrderScreen(),
        },
      ),
      onNotificationLaunch: ({required isFromNotification, required payload}) {
        if (isFromNotification && payload.isNotEmpty) {
          // Route based on notification type
          if (payload['type'] == 'message') {
            return SwiftRouting(
              route: '/message',
              payload: {
                'messageId': payload['messageId'],
                'senderId': payload['senderId'],
              },
            );
          } else if (payload['type'] == 'order') {
            return SwiftRouting(
              route: '/order',
              payload: {
                'orderId': payload['orderId'],
              },
            );
          }
        }
        return null; // Use default route
      },
    );
  }
}

Handling Notification Button Actions #

You can combine both plugins to handle button actions and route accordingly:

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final notifications = SwiftNotifications();
  
  @override
  void initState() {
    super.initState();
    
    // Listen to notification responses (button clicks, taps)
    notifications.onNotificationResponse.listen((response) {
      // Handle button actions
      if (response.actionId == 'view_order') {
        Navigator.pushNamed(context, '/order', arguments: {
          'orderId': response.payload?['orderId'],
        });
      } else if (response.actionId == 'reply') {
        Navigator.pushNamed(context, '/message', arguments: {
          'messageId': response.payload?['messageId'],
        });
      }
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return SwiftFlutterMaterial(
      materialApp: MaterialApp(
        // ... your app config
      ),
      onNotificationLaunch: ({required isFromNotification, required payload}) {
        // Handle cold start routing
        if (isFromNotification) {
          return SwiftRouting(
            route: '/notification',
            payload: payload,
          );
        }
        return null;
      },
    );
  }
}

This combination gives you:

  • Cold start routing via screen_launch_by_notfication
  • Button action handling via swift_notifications
  • Seamless navigation in all app states

🛡️ Error Handling #

The plugin gracefully handles errors:

  • Image download fails: Falls back to text notification
  • Invalid actions: Notification shown without buttons
  • Permission denied: Methods return appropriate error status
  • Network offline: Image notifications fall back to text
  • Cold start events: Automatically stored and delivered when app starts

🐛 Debugging #

Enable verbose logging to debug notification issues:

// Enable verbose logging
SwiftNotifications.enableVerboseLogging();

// Or during initialization
await notifications.initialize(verbose: true);

This will print detailed logs about:

  • Notification creation
  • Image downloads
  • Event handling
  • Cold-start event storage and retrieval
  • Permission status

📝 Examples #

See the example/ directory for a complete working example demonstrating:

  • Simple notifications
  • Image notifications
  • Button notifications
  • Rich notifications (image + buttons)
  • Handling notification responses
  • Permission management
  • Cold-start event handling

🤝 Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License #

See the LICENSE file for details.

🙏 Acknowledgments #

Built with ❤️ for the Flutter community.


One API. Three Platforms. Zero Native Setup.

1
likes
150
points
162
downloads

Publisher

verified publisherswiftflutter.com

Weekly Downloads

A unified Flutter plugin for rich push and local notifications on Android, iOS, and macOS with no native code required.

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on swift_notifications

Packages that implement swift_notifications