screen_launch_by_notfication 2.0.0
screen_launch_by_notfication: ^2.0.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.
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:screen_launch_by_notfication/screen_launch_by_notfication.dart';
Future<void> _initializeNotifications() async {
// Android initialization settings
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
// iOS initialization settings
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
// Combined initialization settings
const InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
// Initialize the plugin
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: _onNotificationTapped,
);
// Request permissions (Android 13+)
if (await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>()
?.requestNotificationsPermission() ??
false) {
// Permission granted
}
// Request permissions (iOS)
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin
>()
?.requestPermissions(alert: true, badge: true, sound: true);
}
void _onNotificationTapped(NotificationResponse response) {
// This is called when a notification is tapped (after Flutter has started)
// The payload is in response.payload
// Store it in native code for consistency using the plugin
if (response.payload != null && response.payload!.isNotEmpty) {
screenLaunchByNotfication.storeNotificationPayload(response.payload!);
}
}
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
final ScreenLaunchByNotfication screenLaunchByNotfication =
ScreenLaunchByNotfication();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize flutter_local_notifications
await _initializeNotifications();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// Example with MaterialApp
return SwiftFlutterMaterial(
materialApp: MaterialApp(
title: 'Screen Launch by Notification',
initialRoute: '/normalSplash',
routes: {
"/normalSplash": (context) => const SplashScreen(),
"/notificationScreen": (context) {
// Get notification payload from the plugin if needed
return const NotificationScreen();
},
"/home": (context) => const HomeScreen(),
},
),
// Custom route logic based on notification launch
// This callback is always called when the app starts
onNotificationLaunch: ({required isFromNotification, required payload}) {
print(
'App launched from notification: $isFromNotification, payload: $payload',
);
if (isFromNotification) {
// Route to notification screen when launched from notification
print('Routing to notification screen');
return '/notificationScreen';
}
// Return null to use initialRoute from MaterialApp
print('Using default initialRoute');
return null;
},
);
// Alternative: Example with GetMaterialApp
// Uncomment to use GetMaterialApp instead:
/*
return SwiftFlutterMaterial(
getMaterialApp: GetMaterialApp(
title: 'Screen Launch by Notification',
initialRoute: '/normalSplash',
routes: {
"/normalSplash": (context) => const SplashScreen(),
"/notificationScreen": (context) => const NotificationScreen(),
"/home": (context) => const HomeScreen(),
},
),
onNotificationLaunch: ({required isFromNotification, required payload}) {
print('App launched from notification: $isFromNotification, payload: $payload');
if (isFromNotification) {
return '/notificationScreen';
}
return null; // Use initialRoute from GetMaterialApp
},
);
*/
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
// Navigate to home after splash delay
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
Navigator.of(context).pushReplacementNamed("/home");
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF667eea), Color(0xFF764ba2)],
),
),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.flutter_dash, size: 100, color: Colors.white),
SizedBox(height: 20),
Text(
'Flutter App',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 10),
Text(
'Loading...',
style: TextStyle(fontSize: 16, color: Colors.white70),
),
],
),
),
),
);
}
}
class NotificationScreen extends StatelessWidget {
final Map<String, dynamic>? payload;
const NotificationScreen({super.key, this.payload});
Map<String, dynamic> getPayloadMap() {
return payload ?? {};
}
@override
Widget build(BuildContext context) {
final payloadMap = getPayloadMap();
return Scaffold(
appBar: AppBar(
title: const Text('Notification Screen'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF4facfe), Color(0xFF00f2fe)],
),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
const Icon(
Icons.notifications_active,
size: 100,
color: Colors.white,
),
const SizedBox(height: 20),
const Text(
'Opened from Notification!',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(
'This screen was opened directly because you tapped a notification. The splash screen was bypassed.',
style: TextStyle(fontSize: 16, color: Colors.white70),
textAlign: TextAlign.center,
),
),
if (payloadMap.isNotEmpty) ...[
const SizedBox(height: 30),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Notification Payload:',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 10),
...payloadMap.entries.map(
(entry) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${entry.key}: ',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Expanded(
child: Text(
entry.value.toString(),
style: const TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
),
],
),
),
),
],
),
),
],
const SizedBox(height: 40),
ElevatedButton(
onPressed: () {
Navigator.of(context).pushReplacementNamed("/home");
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 15,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: const Text(
'Go to Home',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 20),
],
),
),
),
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _isSending = false;
Future<void> _sendTestNotification() async {
setState(() {
_isSending = true;
});
try {
// Create notification details
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'test_notification_channel',
'Test Notifications',
channelDescription: 'Channel for test notifications',
importance: Importance.high,
priority: Priority.high,
showWhen: true,
);
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
// Create payload data
final payload = jsonEncode({
'title': 'Test Notification',
'body': 'This is a test notification payload',
'timestamp': DateTime.now().millisecondsSinceEpoch,
'type': 'test',
});
// Store notification payload using the plugin before sending
await screenLaunchByNotfication.storeNotificationPayload(payload);
// Show notification with payload
await flutterLocalNotificationsPlugin.show(
DateTime.now().millisecondsSinceEpoch.remainder(100000),
'Test Notification',
'Tap to open app from notification',
platformChannelSpecifics,
payload: payload,
);
// Show success message
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Test notification sent! Close the app and tap the notification to test.',
),
duration: Duration(seconds: 3),
backgroundColor: Colors.green,
),
);
}
// Close the app after a short delay
await Future.delayed(const Duration(milliseconds: 500));
if (mounted) {
SystemNavigator.pop();
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
);
}
} finally {
if (mounted) {
setState(() {
_isSending = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.home, size: 100, color: Colors.green),
const SizedBox(height: 20),
const Text(
'Welcome Home!',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
const SizedBox(height: 10),
const Text(
'This is the home screen.',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 40),
ElevatedButton.icon(
onPressed: _isSending ? null : _sendTestNotification,
icon: _isSending
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: const Icon(Icons.notifications_active),
label: Text(
_isSending ? 'Sending...' : 'Send Test Notification',
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 30,
vertical: 15,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
),
const SizedBox(height: 10),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text(
'Tap the button to send a test notification. The app will close, and you can tap the notification to open it again.',
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
),
],
),
),
),
);
}
}