I am developing a Flutter application and using Firebase Cloud Messaging (FCM) for push notifications. When the app is closed, I want to navigate to a specific page within the app when the user clicks on the FCM notification. Currently, the notification only opens the main page of the app. How can I configure my app to open a particular screen based on the notification data when the app is closed?
import 'dart:developer' as dev;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart';
import 'package:flutter_callkit_incoming/entities/entities.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:lawyer/Routes/generated_routes.dart';
import 'package:lawyer/l10n/support_locale.dart';
import 'package:lawyer/provider/call/call_provider.dart';
import 'package:lawyer/provider/chat/chat_detail_provider.dart';
import 'package:lawyer/provider/home/filter_provider.dart';
import 'package:lawyer/provider/home/lawyer_report_provider.dart';
import 'package:lawyer/provider/localization/localization_provider.dart';
import 'package:lawyer/provider/message/message_list_provider.dart';
import 'package:lawyer/provider/navigation/navigation_provider.dart';
import 'package:lawyer/provider/service/user_service_provider.dart';
import 'package:lawyer/static/call_accept_observer.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'firebase_options.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
int getUniqueNotificationId() {
var randomNumber = Random();
var resultOne = randomNumber.nextInt(2000);
var resultTwo = randomNumber.nextInt(100);
if (resultTwo >= resultOne) resultTwo += 1;
return resultTwo;
}
Future<void> backgroundHandler(RemoteMessage message) async {
await handleNotification(message);
return;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// await notificationListeners();
SharedPreferences sharedPrefs;
try {
sharedPrefs = await SharedPreferences.getInstance();
await AwesomeNotifications().initialize(
null,
[
NotificationChannel(
channelKey: 'call_channel',
channelName: 'Calls',
channelDescription: 'Notification channel for calls',
defaultColor: Colors.orange,
importance: NotificationImportance.High,
ledColor: Colors.white,
channelShowBadge: true,
playSound: true,
defaultRingtoneType: DefaultRingtoneType.Ringtone,
enableLights: true,
enableVibration: true,
criticalAlerts: true,
),
NotificationChannel(
channelKey: 'message_channel',
channelName: 'Messages',
channelDescription: 'Notification channel for messages',
defaultColor: Colors.blue,
importance: NotificationImportance.High,
ledColor: Colors.white,
channelShowBadge: true,
defaultRingtoneType: DefaultRingtoneType.Notification,
),
],
);
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
print('${FirebaseMessaging.instance.getToken()}');
FlutterCallkitIncoming.onEvent.listen(
(event) async {
switch (event!.event) {
case Event.actionCallIncoming:
print('Incoming call');
break;
case Event.actionCallStart:
print('Call started');
break;
case Event.actionCallAccept:
print('Call accepted');
// todo Go named
try {
// create a new instance of the provider
await sharedPrefs.setString('callIncoming', 'Accepted');
await AppNavigation.rootNavigatorKey.currentState
?.pushNamed('CallExample');
// Log success or perform additional actions after navigation
} catch (error, stackTrace) {
// Handle or log error
print('Error navigating to CallExample: $error');
print('StackTrace: $stackTrace');
}
return Future.value(true);
case Event.actionCallDecline:
print('Call declined');
break;
case Event.actionCallEnded:
print('Call ended');
break;
case Event.actionCallTimeout:
print('Call timeout');
break;
case Event.actionCallCallback:
print('Call callback');
break;
case Event.actionCallToggleHold:
print('Call hold toggled');
break;
case Event.actionCallToggleMute:
print('Call mute toggled');
break;
case Event.actionCallToggleDmtf:
print('Call DMTF toggled');
break;
case Event.actionCallToggleGroup:
print('Call group toggled');
break;
case Event.actionCallToggleAudioSession:
print('Call audio session toggled');
break;
case Event.actionDidUpdateDevicePushTokenVoip:
print('Device push token updated');
break;
case Event.actionCallCustom:
print('Custom call action');
break;
default:
print('Unknown event type ${event.event}');
}
},
);
} catch (error, stackTrace) {
print('Error getting shared preferences: $error');
print('StackTrace: $stackTrace');
// Optionally initialize sharedPrefs with a default/fallback value if necessary
}
runApp(MyApp());
}
Future<void> handleNotification(RemoteMessage message) async {
String? title = message.data['title'] ?? 'Default Title';
String? body = message.data['body'] ?? 'Default Body';
String channelKey = message.data['channel_key'] ?? 'default_channel';
if (channelKey == 'call_channel') {
// await AwesomeNotifications().createNotification(
// content: NotificationContent(
// id: getUniqueNotificationId(),
// channelKey: channelKey,
// color: Colors.white,
// title: title,
// body: body,
// category: NotificationCategory.Call,
// wakeUpScreen: true,
// fullScreenIntent: true,
// autoDismissible: false,
// backgroundColor: Colors.orange,
// ),
// actionButtons: [
// NotificationActionButton(
// key: 'ACCEPT',
// label: 'Accept Call',
// color: Colors.green,
// autoDismissible: true,
// enabled: true,
// ),
// NotificationActionButton(
// key: 'REJECT',
// label: 'Reject Call',
// color: Colors.redAccent,
// autoDismissible: true,
// ),
// ],
// );
CallKitParams params = const CallKitParams(
id: "21232dgfgbcbgb",
nameCaller: "Coding Is Life",
appName: "Demo",
avatar: "https://i.pravata.cc/100",
handle: "123456",
type: 0,
textAccept: "Accept",
textDecline: "Decline",
duration: 30000,
extra: {'userId': "sdhsjjfhuwhf"},
missedCallNotification: NotificationParams(
callbackText: "Call back", showNotification: false),
android: AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: "#0955fa",
backgroundUrl: "https://i.pravata.cc/500",
actionColor: "#4CAF50",
incomingCallNotificationChannelName: "Incoming call",
missedCallNotificationChannelName: "Missed call"),
ios: IOSParams(
iconName: "Call Demo",
handleType: 'generic',
supportsVideo: true,
maximumCallGroups: 2,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: true,
supportsGrouping: false,
ringtonePath: 'system_ringtone_default'));
await FlutterCallkitIncoming.showCallkitIncoming(params);
} else if (channelKey == 'message_channel') {
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: getUniqueNotificationId(),
channelKey: channelKey,
color: Colors.blue,
title: title,
body: body,
category: NotificationCategory.Message,
backgroundColor: Colors.blue,
autoDismissible: true,
),
actionButtons: [
NotificationActionButton(
key: 'READ',
label: 'Read Message',
color: Colors.green,
autoDismissible: true,
),
NotificationActionButton(
key: 'DISMISS',
label: 'Dismiss',
color: Colors.red,
autoDismissible: true,
),
],
);
}
// await notificationListeners();
return;
}
Future<void> notificationListeners() async {
await AwesomeNotifications().setListeners(
onActionReceivedMethod: (ReceivedAction receivedAction) async {
dev.log('Action Received: ${receivedAction.buttonKeyPressed}');
String? newTitle;
String? newBody;
if (receivedAction.buttonKeyPressed == 'ACCEPT') {
print('ForgotPassword');
Navigator.of(GlobalKey<NavigatorState>().currentContext!)
.pushNamed('ForgotPassword');
Navigator.pushNamed(
GlobalKey<NavigatorState>().currentContext!, '/forgotPassword');
BuildContext context = GlobalKey<NavigatorState>().currentContext!;
Navigator.of(context).pushNamed('ForgotPassword');
print('-->ForgotPassword');
newTitle = 'Call Accepted';
newBody = 'You have accepted the call.';
} else if (receivedAction.buttonKeyPressed == 'REJECT') {
newTitle = 'Call Rejected';
newBody = 'You have rejected the call.';
} else if (receivedAction.buttonKeyPressed == 'READ') {
newTitle = 'Message Read';
newBody = 'You have read the message.';
} else if (receivedAction.buttonKeyPressed == 'DISMISS') {
newTitle = 'Message Dismissed';
newBody = 'You have dismissed the message.';
} else {
dev.log('Unknown Action');
newTitle = 'Unknown Action';
newBody = 'An unknown action was taken.';
}
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: getUniqueNotificationId(),
channelKey: 'response_channel',
color: Colors.purple,
title: newTitle,
body: newBody,
backgroundColor: Colors.purpleAccent,
autoDismissible: true,
),
);
},
);
}
Future<void> callKit() async {}
class MyApp extends StatefulWidget {
MyApp({
super.key,
this.logTitle = 'call accept defoult ',
});
String? logTitle;
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
print('MyApp build');
print(widget.logTitle);
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => FilterProvider()),
ChangeNotifierProvider(create: (context) => MessageListProvider()),
ChangeNotifierProvider(create: (context) => ChatDetailProvider()),
ChangeNotifierProvider(create: (context) => LawyerReportProvider()),
ChangeNotifierProvider(create: (context) => UserServiceProvider()),
ChangeNotifierProvider(create: (context) => CallProvider()),
ChangeNotifierProvider(create: (context) => CallAcceptProvider()),
ChangeNotifierProvider(create: (context) => NavigationProvider()),
ChangeNotifierProvider(create: (context) => LocalizationProvider())
],
child: Consumer<LocalizationProvider>(
builder: (context, provider, child) {
return MaterialApp.router(
color: Color(0xFF000E2B),
title: 'Lawyer',
localizationsDelegates: const [
AppLocalizations.delegate, // Add this line
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
locale: provider.locale,
supportedLocales: L10n.support,
routerConfig: AppNavigation.router,
theme: ThemeData(
fontFamily: 'SF-Pro-Display',
primarySwatch: Colors.green,
appBarTheme: AppBarTheme(
backgroundColor: Color(0xFF000E2B),
elevation: 0,
iconTheme: const IconThemeData(color: Colors.white),
titleTextStyle: const TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.w700,
),
),
scaffoldBackgroundColor: Colors.white,
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Color(0xFF3467E4),
),
tabBarTheme: const TabBarTheme(),
checkboxTheme: CheckboxThemeData(
side: BorderSide.none,
checkColor: const WidgetStatePropertyAll(Colors.white),
fillColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return const Color.fromRGBO(59, 118, 81, 1);
}
return const Color.fromRGBO(152, 152, 152, 1);
},
),
),
dialogBackgroundColor: Colors.white,
),
);
},
),
);
}
}
class CallExample extends StatelessWidget {
const CallExample({super.key});
@override
Widget build(BuildContext context) {
print('Call pAGE sTART');
return MaterialApp(builder: (context, child) {
return Scaffold(
body: Container(
child: Center(
child: Text('Call Example'),
),
),
);
});
}
}