Flutter Screen Launch by Notification - Deep Linking & Navigation Guide
Learn how to detect notification launches, handle deep links, and route directly to specific screens in Flutter apps. Skip splash screens when launched from notifications!
Introduction
One of the most common challenges in Flutter app development is detecting when your app was launched by tapping a notification. Unlike native Android and iOS apps, Flutter doesn’t provide built-in functionality to detect this scenario.
The screen_launch_by_notfication plugin solves this problem by allowing you to:
- Detect when your app was launched from a notification
- Retrieve notification payload data
- Route directly to notification-specific screens
- Skip unnecessary splash screens
- Handle deep links automatically
The Problem
Default Flutter Behavior
By default, Flutter cannot detect whether the app was launched by:
- ❌ Tapping a notification (when app is killed)
- ❌ Tapping a notification (when app is in background)
- ❌ Opening a deep link (when app is not running)
This means users always see the splash screen, even when they tap a notification that should take them directly to a specific screen.
Native App Behavior
Native Android and iOS apps can detect notification launches even when:
- The app is completely terminated
- The app is in the background
- The app is not running at all
The Solution
The screen_launch_by_notfication plugin bridges this gap by:
- Native Code Capture: Native code captures the notification launch event
- Flag Storage: Native code saves a flag and payload in shared preferences
- Early Detection: Flutter reads the flag via MethodChannel before
runApp() - Smart Routing: Flutter decides the initial screen → splash / home / notification screen
Quickstart checklist (copy/paste)
- Add the plugin +
flutter_local_notifications. - Initialize before
runApp(). - Route on first frame using the payload.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final launch = await ScreenLaunchByNotfication.instance.initialize();
runApp(MyApp(
initialRoute: launch.isFromNotification ? '/notification' : '/splash',
payload: launch.payload,
));
}
Installation
Add the package to your pubspec.yaml:
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
Platform Setup
Android Setup
Good News: 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
}
}
Deep Link Configuration (Optional)
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
Good News: 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()
}
}
}
Deep Link Configuration (Optional)
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
Method 1: Using SwiftFlutterMaterial Widget (Recommended)
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(
materialApp: 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,
);
}
return null;
},
onDeepLink: ({required url, required route, required queryParams}) {
if (route == '/product') {
return SwiftRouting(
route: '/product',
payload: {'productId': queryParams['id']},
);
}
return null;
},
);
}
}
Method 2: Manual Detection (Advanced)
If you need more control, you can manually detect notification launches:
import 'package:screen_launch_by_notfication/screen_launch_by_notfication.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Check if app was launched from notification
final launchInfo = await ScreenLaunchByNotification.checkLaunchFromNotification();
String initialRoute = '/splash';
Map<String, dynamic>? initialPayload;
if (launchInfo.isFromNotification) {
initialRoute = '/notification';
initialPayload = launchInfo.payload;
}
runApp(MyApp(
initialRoute: initialRoute,
initialPayload: initialPayload,
));
}
Handling Notification Payload
Basic Payload Access
The payload contains all notification data:
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification && payload != null) {
final notificationId = payload['id'];
final notificationType = payload['type'];
final notificationData = payload['data'];
// Route based on notification type
if (notificationType == 'message') {
return SwiftRouting(
route: '/chat',
payload: {'chatId': notificationData['chatId']},
);
} else if (notificationType == 'order') {
return SwiftRouting(
route: '/order',
payload: {'orderId': notificationData['orderId']},
);
}
}
return null;
}
Complex Payload Structure
// Example notification payload structure
{
"id": "12345",
"type": "message",
"title": "New Message",
"body": "You have a new message",
"data": {
"chatId": "chat_123",
"userId": "user_456",
"timestamp": "2025-01-20T10:00:00Z"
}
}
Deep Link Handling
URL Structure
Deep links follow this structure:
yourapp://route/segment?param1=value1¶m2=value2
Examples:
myapp://product/123myapp://chat/456?userId=789myapp://profile?tab=settings
Deep Link Handler
onDeepLink: ({required url, required route, required queryParams}) {
print('Deep link received: $url');
print('Route: $route');
print('Query params: $queryParams');
// Handle different routes
switch (route) {
case '/product':
return SwiftRouting(
route: '/product',
payload: {'productId': route.split('/').last},
);
case '/chat':
return SwiftRouting(
route: '/chat',
payload: {
'chatId': route.split('/').last,
'userId': queryParams['userId'],
},
);
case '/profile':
return SwiftRouting(
route: '/profile',
payload: {'tab': queryParams['tab']},
);
default:
return null; // Unknown route, use default
}
}
Real-World Examples
E-Commerce App
SwiftFlutterMaterial(
materialApp: MaterialApp(
// ... app config
),
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification && payload != null) {
final type = payload['type'];
switch (type) {
case 'order_status':
return SwiftRouting(
route: '/orders',
payload: {'orderId': payload['orderId']},
);
case 'promotion':
return SwiftRouting(
route: '/products',
payload: {'category': payload['categoryId']},
);
default:
return SwiftRouting(route: '/home');
}
}
return null;
},
)
Chat/Messaging App
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification && payload != null) {
final chatId = payload['data']?['chatId'];
if (chatId != null) {
return SwiftRouting(
route: '/chat',
payload: {
'chatId': chatId,
'messageId': payload['data']?['messageId'],
},
);
}
}
return null;
}
Social Media App
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification && payload != null) {
final notificationType = payload['type'];
if (notificationType == 'new_follower') {
return SwiftRouting(
route: '/profile',
payload: {'userId': payload['data']?['userId']},
);
} else if (notificationType == 'like') {
return SwiftRouting(
route: '/post',
payload: {'postId': payload['data']?['postId']},
);
}
}
return null;
}
Common Use Cases
1. Skip Splash Screen on Notification Launch
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification) {
// Skip splash, go directly to notification screen
return SwiftRouting(
route: '/notification',
payload: payload,
);
}
// Normal launch, show splash
return null;
}
2. Handle Different Notification Types
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification && payload != null) {
final type = payload['type'];
if (type == 'urgent') {
return SwiftRouting(route: '/urgent-notification');
} else if (type == 'reminder') {
return SwiftRouting(route: '/reminders');
} else {
return SwiftRouting(route: '/notifications');
}
}
return null;
}
3. Pass Complex Data
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification && payload != null) {
return SwiftRouting(
route: '/details',
payload: {
'id': payload['id'],
'type': payload['type'],
'data': payload['data'],
'timestamp': DateTime.now().toIso8601String(),
},
);
}
return null;
}
Best Practices
1. Always Check Payload
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification && payload != null) {
// Always validate payload structure
if (payload.containsKey('type') && payload.containsKey('data')) {
// Handle notification
}
}
return null;
}
2. Provide Fallback Routes
onNotificationLaunch: ({required isFromNotification, required payload}) {
if (isFromNotification) {
try {
// Try to route based on notification
return SwiftRouting(
route: '/notification',
payload: payload,
);
} catch (e) {
// Fallback to home on error
return SwiftRouting(route: '/home');
}
}
return null;
}
3. Handle Deep Links Securely
onDeepLink: ({required url, required route, required queryParams}) {
// Validate deep link
if (route.startsWith('/public')) {
// Public routes are safe
return SwiftRouting(route: route, payload: queryParams);
} else if (route.startsWith('/private')) {
// Check authentication first
if (isAuthenticated) {
return SwiftRouting(route: route, payload: queryParams);
} else {
return SwiftRouting(route: '/login');
}
}
return null;
}
Troubleshooting
Notification Not Detected
Problem: App doesn’t detect notification launch.
Solutions:
- Ensure notification permissions are granted
- Check that
flutter_local_notificationsis properly configured - Verify notification payload structure
- Check AndroidManifest.xml / Info.plist configuration
Deep Link Not Working
Problem: Deep links don’t open the app.
Solutions:
- Verify URL scheme is correctly configured
- Check intent filters in AndroidManifest.xml
- Verify CFBundleURLSchemes in Info.plist
- Test with
adb shell am start -W -a android.intent.action.VIEW -d "yourapp://test"
Payload is Null
Problem: Payload is null when notification is tapped.
Solutions:
- Ensure notification has a data payload
- Check notification payload structure matches expected format
- Verify notification is sent with proper data field
Testing
Test Notification Launch (Android)
# Send test notification
adb shell am broadcast -a com.google.firebase.MESSAGING_EVENT \
--es "notification" '{"title":"Test","body":"Test notification","data":{"type":"test"}}'
Test Deep Link (Android)
adb shell am start -W -a android.intent.action.VIEW \
-d "yourapp://product/123"
Test Deep Link (iOS)
xcrun simctl openurl booted "yourapp://product/123"
Performance Considerations
- Fast Detection: The plugin checks notification launch before
runApp(), ensuring minimal delay - Payload Size: Keep payload sizes reasonable (< 4KB recommended)
- Navigation: Direct routing skips unnecessary screens, improving perceived performance
Conclusion
The screen_launch_by_notfication plugin provides a seamless way to handle notification launches and deep links in Flutter apps. By detecting notification launches before the app initializes, you can provide a native-like experience that routes users directly to relevant screens.
Key benefits:
- ✅ Detect notification launches in all app states
- ✅ Zero native setup required
- ✅ Automatic deep link handling
- ✅ Works with MaterialApp and GetMaterialApp
- ✅ Cross-platform support (iOS & Android)
Resources
Happy Building! 🚀
Explore More
Quick Links
Related Posts
Swift Animations for Flutter - Complete Guide to Declarative Animations
Learn how to create SwiftUI-like declarative animations in Flutter with zero boilerplate. No controllers, no ticker providers required!
January 15, 2025
Adaptive Micro Factory Model Analysis: Flexible Manufacturing for Automotive (2024-2030)
A complete guide to adaptive micro factory economics, architecture, ROI, use-cases, and deployment models for automotive manufacturers.
February 20, 2025
AMR Deployment Cost Breakdown for Automotive Plants: ROI Analysis & Implementation Guide (2025)
Complete cost breakdown for AMR deployment in automotive manufacturing. Hardware, software, integration, ROI analysis, payback period, fleet cost, and benchmarks. Includes real case studies and implementation timelines.
February 20, 2025
Automation CAPEX vs OPEX in Automotive: Complete Financial Analysis & Decision Framework
Complete financial guide for automotive CAPEX vs OPEX automation models, including ROI analysis, cost-per-unit comparison, case studies, and decision framework.
February 20, 2025