screen_launch_by_notfication 2.3.2 copy "screen_launch_by_notfication: ^2.3.2" to clipboard
screen_launch_by_notfication: ^2.3.2 copied to clipboard

A Flutter plugin to detect if the app was launched by tapping a notification or deep link, retrieve notification payload, and enable automatic routing. Supports both notification launches and custom d [...]

screen_launch_by_notfication #

A Flutter plugin that detects if your app was launched by tapping a notification and retrieves the notification payload. This enables you to skip splash screens and route directly to notification-specific screens, just like native apps.

📚 Learn more: swiftflutter.com/dynamicnotification

Features #

Detect notification launches - Know when your app was opened from a notification
Deep link support - Handle custom URL schemes and universal links automatically
Retrieve notification payload - Get all notification data including custom payload
Skip splash screens - Route directly to notification/deep link screens
Works in all app states - Detects notification taps and deep links when app is killed, in background, or foreground
Cross-platform - Works on both Android and iOS
Zero native setup - All native code handled automatically, just configure AndroidManifest.xml and Info.plist
Compatible with flutter_local_notifications - Works seamlessly with the popular notification plugin

Overview #

Flutter by default cannot detect whether the app was launched by tapping a notification. However, Android & iOS natively can detect this even when the app is:

  • ❌ Killed (terminated)
  • ❌ In background
  • ❌ Not running at all

This plugin bridges that gap by:

  1. Native code captures the notification launch event
  2. Native code saves a flag and payload
  3. Flutter reads the flag via MethodChannel before runApp()
  4. Flutter decides the initial screen → splash / home / notification screen

Installation #

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

dependencies:
  screen_launch_by_notfication: ^2.3.0
  flutter_local_notifications: ^19.5.0  # Recommended for sending notifications
  get: ^4.6.6  # Required only if using GetMaterialApp

Then run:

flutter pub get

Android Setup #

No native code setup required! 🎉 The plugin handles everything automatically.

Just ensure you have the required dependencies in your android/app/build.gradle.kts:

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

To enable deep linking, add intent filters to your android/app/src/main/AndroidManifest.xml:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop">
    <!-- ... other intent filters ... -->
    
    <!-- Deep link intent filter -->
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="yourapp" />
    </intent-filter>
</activity>

Replace yourapp with your custom scheme (e.g., myapp, notificationapp).

iOS Setup #

No native code setup required! 🎉 The plugin handles everything automatically.

For iOS, you only need to request notification permissions in your app (if you haven't already):

import UserNotifications

// In your AppDelegate or wherever you request permissions
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
      if granted {
    DispatchQueue.main.async {
      UIApplication.shared.registerForRemoteNotifications()
    }
  }
}

To enable deep linking, add URL scheme to your ios/Runner/Info.plist:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yourapp</string>
        </array>
    </dict>
</array>

Replace yourapp with your custom scheme (e.g., myapp, notificationapp).

Note: The plugin automatically handles all notification detection, payload storage, and deep link handling. No need to modify AppDelegate.swift or MainActivity.kt!

Usage #

The easiest way to use this plugin is with the SwiftFlutterMaterial widget. Simply wrap your existing MaterialApp or GetMaterialApp:

With MaterialApp

import 'package:flutter/material.dart';
import 'package:screen_launch_by_notfication/screen_launch_by_notfication.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return SwiftFlutterMaterial(
      materialApp: MaterialApp(
        title: 'My App',
        theme: ThemeData(primarySwatch: Colors.blue),
      initialRoute: '/splash',
      routes: {
        '/splash': (_) => SplashScreen(),
        '/notification': (_) => NotificationScreen(),
        '/home': (_) => HomeScreen(),
      },
      ),
      onNotificationLaunch: ({required isFromNotification, required payload}) {
        if (isFromNotification) {
          return SwiftRouting(
            route: '/notification',
            payload: payload, // Pass full payload or null
          );
        }
        return null; // Use MaterialApp's initialRoute
      },
      onDeepLink: ({required url, required route, required queryParams}) {
        // Handle deep links (e.g., myapp://product/123)
        if (route == '/product') {
          return SwiftRouting(
            route: '/product',
            payload: {'productId': queryParams['id']},
          );
        }
        return null; // Skip navigation for unknown routes
      },
    );
  }
}

With GetMaterialApp

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:screen_launch_by_notfication/screen_launch_by_notfication.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return SwiftFlutterMaterial(
      getMaterialApp: GetMaterialApp(
        title: 'My App',
        theme: ThemeData(primarySwatch: Colors.blue),
        initialRoute: '/splash',
        getPages: [
          GetPage(name: '/splash', page: () => SplashScreen()),
          GetPage(name: '/notification', page: () => NotificationScreen()),
          GetPage(name: '/home', page: () => HomeScreen()),
        ],
      ),
      onNotificationLaunch: ({required isFromNotification, required payload}) {
        if (isFromNotification) {
          return SwiftRouting(
            route: '/notification',
            payload: payload, // Pass full payload or null
          );
        }
        return null; // Use GetMaterialApp's initialRoute
      },
      onDeepLink: ({required url, required route, required queryParams}) {
        // Handle deep links (e.g., myapp://product/123)
        if (route == '/product') {
          return SwiftRouting(
            route: '/product',
            payload: {'productId': queryParams['id']},
          );
        }
        return null; // Skip navigation for unknown routes
      },
    );
  }
}

Key Features:

  • ✅ Pass your existing MaterialApp or GetMaterialApp - no need to duplicate properties
  • ✅ All routing properties (routes, getPages, initialRoute, etc.) are automatically managed
  • ✅ Zero native code setup required (just configure AndroidManifest.xml and Info.plist for deep links)
  • ✅ Works with your existing app structure
  • ✅ Automatic deep link handling with onDeepLink callback
  • ✅ Deep links take priority over notifications when both are present

The plugin automatically handles deep links in both cold state (app closed) and warm state (app running):

SwiftFlutterMaterial(
  materialApp: MaterialApp(
    routes: {
      '/home': (context) => HomeScreen(),
      '/product': (context) => ProductScreen(),
    },
  ),
  onDeepLink: ({required url, required route, required queryParams}) {
    // Handle product deep links: myapp://product/123 or myapp://product?id=123
    if (route == '/product' || route.startsWith('/product/')) {
      final productId = route.split('/').last;
      return SwiftRouting(
        route: '/product',
        payload: {
          'productId': productId,
          'source': 'deeplink',
          ...queryParams, // Include all query params
        },
      );
    }
    return null; // Skip navigation for unknown routes
  },
)

Deep Link Formats Supported:

  • Custom schemes: myapp://product/123, myapp://product?id=123
  • Universal links: https://yourapp.com/product/123
  • Path-based: /product/123/product (with ID in payload)
  • Query-based: /product?id=123/product (with ID in queryParams)

Testing Deep Links:

Android:

adb shell am start -a android.intent.action.VIEW -d "myapp://product/123"

iOS (Simulator):

xcrun simctl openurl booted "myapp://product/123"

Learn more about SwiftFlutterMaterial at swiftflutter.com/dynamicnotification

Basic Usage (Manual) #

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:screen_launch_by_notfication/screen_launch_by_notfication.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final screenLaunchByNotfication = ScreenLaunchByNotfication();
  final result = await screenLaunchByNotfication.isFromNotification();
  
  final bool openFromNotification = result['isFromNotification'] ?? false;
  final String payload = result['payload'] ?? '{}';

  String initialRoute = openFromNotification
      ? "/notificationScreen"
      : "/normalSplash";

  runApp(MyApp(initialRoute: initialRoute, notificationPayload: payload));
}

With flutter_local_notifications #

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> sendNotification() async {
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'channel_id',
    'Channel Name',
    channelDescription: 'Channel Description',
    importance: Importance.high,
    priority: Priority.high,
  );

  const DarwinNotificationDetails iOSPlatformChannelSpecifics =
      DarwinNotificationDetails(
    presentAlert: true,
    presentBadge: true,
    presentSound: true,
  );

  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
    iOS: iOSPlatformChannelSpecifics,
  );

  final payload = jsonEncode({
    'title': 'Test Notification',
    'body': 'This is a test',
    'timestamp': DateTime.now().millisecondsSinceEpoch,
  });

  // Store payload before sending notification
  final screenLaunchByNotfication = ScreenLaunchByNotfication();
  await screenLaunchByNotfication.storeNotificationPayload(payload);

  await flutterLocalNotificationsPlugin.show(
    DateTime.now().millisecondsSinceEpoch.remainder(100000),
    'Test Notification',
    'Tap to open app',
    platformChannelSpecifics,
    payload: payload,
  );
}

Displaying Notification Payload #

class NotificationScreen extends StatelessWidget {
  final String payload;

  const NotificationScreen({super.key, required this.payload});

  Map<String, dynamic> getPayloadMap() {
    try {
      return jsonDecode(payload) as Map<String, dynamic>;
    } catch (e) {
      return {};
    }
  }

  @override
  Widget build(BuildContext context) {
    final payloadMap = getPayloadMap();

    return Scaffold(
      appBar: AppBar(title: const Text('Notification Screen')),
      body: payloadMap.isNotEmpty
          ? ListView(
              padding: const EdgeInsets.all(16),
              children: payloadMap.entries.map((entry) {
                return ListTile(
                  title: Text(entry.key),
                  subtitle: Text(entry.value.toString()),
                );
              }).toList(),
            )
          : const Center(child: Text('No payload data')),
    );
  }
}

API Reference #

isFromNotification() #

Checks if the app was launched from a notification tap.

Returns: Future<Map<String, dynamic>>

  • isFromNotification (bool): Whether the app was opened from a notification
  • payload (String): The notification payload as a JSON string

Example:

final result = await screenLaunchByNotfication.isFromNotification();
if (result['isFromNotification'] == true) {
  final payload = jsonDecode(result['payload']);
  print('Opened from notification with payload: $payload');
}

storeNotificationPayload(String payload) #

Stores notification payload in native storage for later retrieval.

Parameters:

  • payload (String): JSON string containing the notification payload

Returns: Future<bool> - true if successful

Example:

final payload = jsonEncode({'title': 'Test', 'body': 'Message'});
await screenLaunchByNotfication.storeNotificationPayload(payload);

Handles deep link routing when app is launched from a deep link or receives a deep link while running.

Parameters:

  • url (String): The full deep link URL (e.g., myapp://product/123)
  • route (String): The parsed route path (e.g., /product/123 or /product)
  • queryParams (Map<String, dynamic>): Query parameters from the URL

Returns: SwiftRouting? - Return a SwiftRouting object to navigate, or null to skip navigation

Example:

onDeepLink: ({required url, required route, required queryParams}) {
  // Handle path-based routes: myapp://product/123
  if (route.startsWith('/product/')) {
    final productId = route.split('/').last;
    return SwiftRouting(
      route: '/product',
      payload: {'productId': productId},
    );
  }
  
  // Handle query-based routes: myapp://product?id=123
  if (route == '/product' && queryParams.containsKey('id')) {
    return SwiftRouting(
      route: '/product',
      payload: {'productId': queryParams['id']},
    );
  }
  
  return null; // Skip navigation for unknown routes
}

How It Works #

  1. User taps notification → Native code captures the launch event
  2. Native code saves flagopenFromNotification = true in SharedPreferences/UserDefaults
  3. Native code saves payload → Notification data stored as JSON
  4. Flutter starts → Reads flag via MethodChannel before runApp()
  5. Flutter decides route → Bypasses splash if opened from notification

Result #

  • Normal launch: Splash → Home
  • Notification launch: Directly to NotificationScreen (No Splash)

Requirements #

  • Flutter SDK: >=3.3.0
  • Dart SDK: ^3.10.0
  • Android: Minimum SDK 21 (Android 5.0)
  • iOS: Minimum iOS 10.0

Contributing #

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

License #

This project is licensed under the MIT License - see the LICENSE file for details.

SwiftFlutterMaterial Widget #

Version 2.3.0 introduces deep link support and enhanced notification handling:

New in 2.3.0:

  • 🔗 Deep Link Support: Full support for custom URL schemes and universal links
  • 🎯 Automatic Deep Link Handling: Deep links work in both cold and warm states
  • 📦 Zero Native Setup: All deep link handling done in plugin - just configure manifests
  • onDeepLink Callback: Custom routing logic for deep links

Features from 2.1.0:

  • Real-time Navigation: Automatically navigates when notification is tapped while app is running
  • Dynamic Routing: onNotificationLaunch callback works for both initial launch and runtime taps
  • Event Stream: getNotificationStream() method for listening to notification events

Features (from 2.0.0):

  • ✅ Accepts MaterialApp or GetMaterialApp instances
  • ✅ Zero native code setup required
  • ✅ Automatic route management
  • ✅ Full GetX navigation support
  • ✅ Works seamlessly with existing app structure
  • ✅ Custom routing based on conditions via onNotificationLaunch callback
  • ✅ All MaterialApp/GetMaterialApp properties are preserved and managed
  • ✅ Loading state while checking notification status
  • ✅ Error handling with fallback to initial route
  • ✅ Self-contained native implementation

Example with dynamic routing:

SwiftFlutterMaterial(
  materialApp: MaterialApp(
  routes: {
      '/home': (context) => HomeScreen(),
      '/chatPage': (context) => ChatScreen(),
      '/notificationScreen': (context) => NotificationScreen(),
    },
  ),
  onNotificationLaunch: ({required isFromNotification, required payload}) {
    // Works for both initial launch AND runtime notification taps
    if (payload.containsKey('chatnotification')) {
      return '/chatPage';  // Route to chat screen
    }
    if (isFromNotification) {
      return '/notificationScreen';  // Route to notification screen
    }
    return null;  // Use default initialRoute
  },
)

Learn more about SwiftFlutterMaterial at swiftflutter.com/dynamicnotification

☕ Support the Project #

Love using Screen Launch by Notification? Your support helps me create more amazing plugins, add new features, and keep improving the developer experience!

Buy Me A Coffee

Your contributions enable me to:

  • 🚀 Build more Flutter plugins and packages
  • ✨ Add exciting new features and improvements
  • 🐛 Fix bugs faster and maintain high quality
  • 📚 Create better documentation and examples
  • 💡 Explore innovative ideas for the Flutter ecosystem

Every contribution, big or small, makes a difference! Thank you for supporting open-source development! 🙏

Support on Buy Me a Coffee →

Support & Documentation #

📚 Documentation: swiftflutter.com/dynamicnotification

If you encounter any issues or have questions, please file an issue on the GitHub repository.

27
likes
140
points
392
downloads

Publisher

verified publisherswiftflutter.com

Weekly Downloads

A Flutter plugin to detect if the app was launched by tapping a notification or deep link, retrieve notification payload, and enable automatic routing. Supports both notification launches and custom deep links with zero native code setup.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, get, plugin_platform_interface

More

Packages that depend on screen_launch_by_notfication

Packages that implement screen_launch_by_notfication