screen_launch_by_notfication 1.0.0
screen_launch_by_notfication: ^1.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';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize flutter_local_notifications
await _initializeNotifications();
const channel = MethodChannel("launch_channel");
final dynamic response =
await channel.invokeMethod("isFromNotification") ?? {};
final bool openFromNotification =
response is Map ? (response["isFromNotification"] ?? false) : false;
final String payload =
response is Map ? (response["payload"] ?? "{}") : "{}";
String initialRoute = openFromNotification
? "/notificationScreen"
: "/normalSplash";
runApp(MyApp(initialRoute: initialRoute, notificationPayload: payload));
}
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
if (response.payload != null && response.payload!.isNotEmpty) {
const channel = MethodChannel("launch_channel");
channel.invokeMethod("storeNotificationPayload", response.payload);
}
}
class MyApp extends StatelessWidget {
final String initialRoute;
final String notificationPayload;
const MyApp({
super.key,
required this.initialRoute,
required this.notificationPayload,
});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Screen Launch by Notification',
initialRoute: initialRoute,
routes: {
"/normalSplash": (_) => const SplashScreen(),
"/notificationScreen": (_) => NotificationScreen(
payload: notificationPayload,
),
"/home": (_) => const HomeScreen(),
},
);
}
}
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 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'),
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> {
final MethodChannel _channel = const MethodChannel("launch_channel");
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',
});
// Show notification with payload
await flutterLocalNotificationsPlugin.show(
DateTime.now().millisecondsSinceEpoch.remainder(100000),
'Test Notification',
'Tap to open app from notification',
platformChannelSpecifics,
payload: payload,
);
// Store notification info in native code for detection
await _channel.invokeMethod("storeNotificationPayload", 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,
),
),
],
),
),
),
);
}
}