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

A Flutter plugin to detect if the app was launched by tapping a notification and retrieve notification payload, enabling splash screen bypass and direct routing to notification screens.

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
Retrieve notification payload - Get all notification data including custom payload
Skip splash screens - Route directly to notification screens when opened from notification
Works in all app states - Detects notification taps when app is killed, in background, or foreground
Cross-platform - Works on both Android and iOS
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: ^1.1.0
  flutter_local_notifications: ^19.5.0  # Recommended for sending notifications

Then run:

flutter pub get

Android Setup #

  1. Enable core library desugaring in android/app/build.gradle.kts:
android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}
  1. Update your MainActivity.kt to detect notification taps:
package com.example.your_app

import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import org.json.JSONObject

class MainActivity : FlutterActivity() {
    private val CHANNEL = "launch_channel"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        checkNotificationIntent(intent)
    }

    override fun onNewIntent(intent: android.content.Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        checkNotificationIntent(intent)
    }

    private fun checkNotificationIntent(intent: android.content.Intent) {
        val prefs = getSharedPreferences("launchStore", MODE_PRIVATE)

        // Check if app opened by notification tap
        val isFromFlutterNotification = intent.action == "com.dexterous.flutterlocalnotifications.NOTIFICATION_TAPPED" ||
                intent.hasExtra("notification_launch_app")
        val isFromCustomNotification = intent.extras?.getBoolean("fromNotification") == true
        val hasPayload = intent.extras?.containsKey("payload") == true

        if (isFromFlutterNotification || isFromCustomNotification || hasPayload) {
            prefs.edit().putBoolean("openFromNotification", true).apply()

            // Extract notification payload
            val payload = JSONObject()
            
            val flutterPayload = intent.extras?.getString("payload")
            if (!flutterPayload.isNullOrEmpty()) {
                try {
                    val payloadObj = JSONObject(flutterPayload)
                    payloadObj.keys().forEach { key ->
                        payload.put(key, payloadObj.get(key))
                    }
                } catch (e: Exception) {
                    payload.put("payload", flutterPayload)
                }
            }
            
            val storedPayload = prefs.getString("pendingNotificationPayload", null)
            if (storedPayload != null) {
                try {
                    val storedObj = JSONObject(storedPayload)
                    storedObj.keys().forEach { key ->
                        if (!payload.has(key)) {
                            payload.put(key, storedObj.get(key))
                        }
                    }
                    prefs.edit().remove("pendingNotificationPayload").apply()
                } catch (e: Exception) {
                    // Ignore
                }
            }
            
            intent.extras?.keySet()?.forEach { key ->
                if (key != "payload") {
                    when (val value = intent.extras?.get(key)) {
                        is String -> payload.put(key, value)
                        is Int -> payload.put(key, value)
                        is Boolean -> payload.put(key, value)
                        is Double -> payload.put(key, value)
                        else -> payload.put(key, value.toString())
                    }
                }
            }
            
            if (payload.length() > 0) {
                prefs.edit().putString("notificationPayload", payload.toString()).apply()
            }
        }
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "isFromNotification" -> {
                        val prefs = getSharedPreferences("launchStore", MODE_PRIVATE)
                        val isFromNotification = prefs.getBoolean("openFromNotification", false)
                        val payload = prefs.getString("notificationPayload", null)
                        
                        val response = mapOf(
                            "isFromNotification" to isFromNotification,
                            "payload" to (payload ?: "{}")
                        )
                        
                        result.success(response)

                        prefs.edit()
                            .putBoolean("openFromNotification", false)
                            .putString("notificationPayload", null)
                            .apply()
                    }
                    "storeNotificationPayload" -> {
                        try {
                            val payload = call.arguments as? String ?: "{}"
                            val prefs = getSharedPreferences("launchStore", MODE_PRIVATE)
                            prefs.edit().putString("pendingNotificationPayload", payload).apply()
                            result.success(true)
                        } catch (e: Exception) {
                            result.error("ERROR", "Failed to store payload: ${e.message}", null)
                        }
                    }
                    else -> result.notImplemented()
                }
            }
    }
}

iOS Setup #

Update your AppDelegate.swift:

import Flutter
import UIKit
import UserNotifications

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
      if granted {
        DispatchQueue.main.async {
          application.registerForRemoteNotifications()
        }
      }
    }
    
    UNUserNotificationCenter.current().delegate = self
    
    if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
      UserDefaults.standard.set(true, forKey: "openFromNotification")
      if let jsonData = try? JSONSerialization.data(withJSONObject: notification),
         let jsonString = String(data: jsonData, encoding: .utf8) {
        UserDefaults.standard.set(jsonString, forKey: "notificationPayload")
      }
      UserDefaults.standard.synchronize()
    }
    
    let result = super.application(application, didFinishLaunchingWithOptions: launchOptions)
    
    DispatchQueue.main.async {
      if let controller = self.window?.rootViewController as? FlutterViewController {
        let channel = FlutterMethodChannel(name: "launch_channel",
                                           binaryMessenger: controller.binaryMessenger)
        
        channel.setMethodCallHandler { call, result in
          if call.method == "isFromNotification" {
            let flag = UserDefaults.standard.bool(forKey: "openFromNotification")
            let payload = UserDefaults.standard.string(forKey: "notificationPayload") ?? "{}"
            
            let response: [String: Any] = [
              "isFromNotification": flag,
              "payload": payload
            ]
            
            result(response)
            
            UserDefaults.standard.set(false, forKey: "openFromNotification")
            UserDefaults.standard.removeObject(forKey: "notificationPayload")
            UserDefaults.standard.synchronize()
          } else if call.method == "storeNotificationPayload" {
            if let payload = call.arguments as? String {
              UserDefaults.standard.set(payload, forKey: "pendingNotificationPayload")
              UserDefaults.standard.synchronize()
              result(true)
            } else {
              result(FlutterMethodNotImplemented)
            }
          } else {
            result(FlutterMethodNotImplemented)
          }
        }
      }
    }
    
    return result
  }
  
  override func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
  ) {
    UserDefaults.standard.set(true, forKey: "openFromNotification")
    
    var payloadString: String?
    
    if let payload = response.notification.request.content.userInfo["payload"] as? String {
      payloadString = payload
    } else {
      payloadString = UserDefaults.standard.string(forKey: "pendingNotificationPayload")
      UserDefaults.standard.removeObject(forKey: "pendingNotificationPayload")
    }
    
    if let payload = payloadString {
      UserDefaults.standard.set(payload, forKey: "notificationPayload")
    } else {
      let userInfo = response.notification.request.content.userInfo
      if let jsonData = try? JSONSerialization.data(withJSONObject: userInfo),
         let jsonString = String(data: jsonData, encoding: .utf8) {
        UserDefaults.standard.set(jsonString, forKey: "notificationPayload")
      }
    }
    
    UserDefaults.standard.synchronize()
    
    completionHandler()
  }
  
  override func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    willPresent notification: UNNotification,
    withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
  ) {
    UserDefaults.standard.set(true, forKey: "openFromNotification")
    
    if let payload = notification.request.content.userInfo["payload"] as? String {
      UserDefaults.standard.set(payload, forKey: "notificationPayload")
    } else {
      let userInfo = notification.request.content.userInfo
      if let jsonData = try? JSONSerialization.data(withJSONObject: userInfo),
         let jsonString = String(data: jsonData, encoding: .utf8) {
        UserDefaults.standard.set(jsonString, forKey: "notificationPayload")
      }
    }
    
    UserDefaults.standard.synchronize()
    
    completionHandler([.banner, .sound, .badge])
  }
}

Usage #

The easiest way to use this plugin is with the SwiftFlutterMaterial widget:

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(
      initialRoute: '/splash',
      homeRoute: '/home',
      routes: {
        '/splash': (_) => SplashScreen(),
        '/notification': (_) => NotificationScreen(),
        '/home': (_) => HomeScreen(),
      },
      onNotificationLaunch: ({required isFromNotification, required payload}) {
        if (isFromNotification) {
          return '/notification';
        }
        return null;
      },
    );
  }
}

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);

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 1.1.0 introduces SwiftFlutterMaterial, a powerful widget that automatically handles notification-based routing:

SwiftFlutterMaterial(
  initialRoute: '/splash',
  homeRoute: '/home',
  routes: {
    '/splash': (_) => SplashScreen(),
    '/notification': (_) => NotificationScreen(),
    '/home': (_) => HomeScreen(),
  },
  onNotificationLaunch: ({required isFromNotification, required payload}) {
    if (isFromNotification) {
      return '/notification';
    }
    return null; // Use initialRoute
  },
)

Features:

  • ✅ Automatic notification detection
  • ✅ Custom routing based on conditions
  • ✅ Smart back navigation (goes to home instead of exiting)
  • ✅ Payload access in routes via routesWithPayload
  • ✅ Loading state while checking notification status
  • ✅ Error handling with fallback to initial route

Learn more about SwiftFlutterMaterial at swiftflutter.com/dynamicnotification

Support #

📚 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 and retrieve notification payload, enabling splash screen bypass and direct routing to notification screens.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on screen_launch_by_notfication

Packages that implement screen_launch_by_notfication