Flutter Screen Launch by Notification: Complete Deep Linkin
Learn how to detect notification launches, handle deep links, and route directly to specific screens in Flutter apps. Learn real strategies, costs, and
Updated: January 20, 2025
Flutter Screen Launch by Notification: Complete Deep Linking & Navigation Guide
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
🎯 Complete Guide
This article is part of our comprehensive series. Read the complete guide:
Read: Swift Animations for Flutter - Complete Guide to Declarative AnimationsQuick Links
Related Posts
Swift Animations for Flutter: Complete Guide to Declarative
Learn how to create SwiftUI-like declarative animations in Flutter with zero boilerplate. No controllers, no ticker providers required!
January 15, 2025
Automated Inspection Cells for Automotive & Electronics: Build vs Buy and the Hidden Integration Costs
Turnkey inspection cells often triple in cost after PLC/MES, fixturing, safety, and validation. Use this build vs buy matrix, 5-year TCO math, and vendor scorecard.
January 7, 2026
Packaging Line Automation for CPG & Pharma: Quick Wins That Pay Back in Under a Year
5 retrofit-friendly packaging line automation quick wins (vision, case packing, palletizing, smart conveyors, labeling) that can pay back in 3–12 months.
January 7, 2026
Robotics Safety Playbook: Avoid the Top 10 Deployment Incidents
Avoid the top 10 robotics deployment incidents with a safety playbook. Covers zoning, interlocks, training, and change management for operators.
January 7, 2026