My App does the following: It runs a background Task using Flutter Workmanager which checks some values and then it throws a Notification via Flutter Local Notification. In the initialize method from FlutterLocalNotifications Plugin, i can specify a inline fuction, which should navigate to a page. Since i dont have a Builder context, i must use a Navigator Key with OnGenerateRoute to forward the user to a site. However, this doesn`t work and i don´t know why. I know that this code is useful when the app gotkilled.
Example Code
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
String initialRoute = HomePage.routeName;
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
But what to do when the app is still alive? My Project code is listed below.
Main.Dart
void main() {
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
Workmanager().registerPeriodicTask("1", "test",frequency: Duration(minutes: 15));
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: "/",
navigatorKey: NavigationService.navigatorKey,
onGenerateRoute: RouteGenerator.generateRoute
);
}
}
RouteGenerator.dart
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch(settings.name) {
case '/first':
return MaterialPageRoute(builder: (_) => Page1(title: "First"));
case '/second':
return MaterialPageRoute(builder: (_) => Page2(title: "Second"));
case '/third':
return MaterialPageRoute(builder: (_) => Page3(title: "Third"));
case '/fourth':
return MaterialPageRoute(builder: (_) => Page4(title: "Fourth"));
}
return MaterialPageRoute(builder: (_) => Page0(title: "Root!"));
}
}
class NavigationService {
static final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
static Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!.pushNamed(routeName);
}
}
service.dart
class DevHttpOverrides extends HttpOverrides {
#override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
}
}
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async{
HttpOverrides.global = new DevHttpOverrides();
var url = 'https://172.16.0.100/handler.php?page=settings';
http.Response response = await http.get(Uri.parse(url));
List<dynamic> list = jsonDecode(response.body);
SharedPreferences prefs = await SharedPreferences.getInstance();
var usage = "Beides";
var checkValue = "temp_out";
var borderValueString = "14.9";
var checktype = "Grenzwert überschreiten";
var borderValueDouble;
var message = "";
if(usage != "Nur Home Widgets" && checkValue != "" && borderValueString != "" && checktype != "")
{
var value = list[0][checkValue];
if (double.tryParse(borderValueString) != null && double.tryParse(value) != null)
{
borderValueDouble = double.parse(borderValueString);
value = double.parse(value);
}
if (checktype == "Grenzwert unterschreiten")
{
if (borderValueDouble is double)
{
if (value <= borderValueDouble)
{
message = "Grenzwert unterschritten";
}
}
}
else if (checktype == "Grenzwert überschreiten")
{
if (borderValueDouble is double)
{
if (value >= borderValueDouble)
{
message = "Grenzwert überschritten";
}
}
}
else if (checktype == "Entspricht Grenzwert")
{
if (borderValueDouble == value)
{
message = "Grenzwert erreicht";
}
}
}
if(message != "")
{
FlutterLocalNotificationsPlugin flip = new FlutterLocalNotificationsPlugin();
var android = new AndroidInitializationSettings('#mipmap/ic_launcher');
var ios = new IOSInitializationSettings();
var settings = new InitializationSettings(android: android, iOS: ios);
flip.initialize(settings, onSelectNotification: (String? payload) async {
await NavigationService.navigatorKey.currentState!.push(MaterialPageRoute(builder: (context) => Page4(title: "Hello")));
});
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
'1',
'weatherstation',
'Notify when values change',
importance: Importance.max,
priority: Priority.high
);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics);
await flip.show(0, message,
'App öffnen für weitere Details',
platformChannelSpecifics, payload: 'Default_Sound'
);
}
return Future.value(true);
});
}
Did you find any solution for it? I'm also working on an Android application that update user data in firestore database (at interval of 15min) and send user its notification (both task happens in background using flutter workmanager_plugin). When user taps on the notification he should be navigated to the route which shows latest data from the database.
First 2 background tasks are happening successfully but when the notification is clicked nothing is happening. I'm also using GlobalKey key to get MaterialApp widget's context, so that routing Route can be pushed.
It seems like onSelectNotification property for FlutterLocalNotificationsPlugin.initialize() method don't work for workmanager plugin. I have also added a print statement inside it, but nothing get displayed in console. I thought maybe my Globalkey has some fault but when I tried it for navigating pages in non background task it was happening succesfully, similarly onSelectNotification was working perfectely for non-workmanager task.
void callbackDispatcher() {
Workmanager().executeTask((task, data) async {
if(task=='showNotification'){
FlutterLocalNotificationsPlugin notifPlugin =
FlutterLocalNotificationsPlugin();
NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
'main_channel',
'Main Channel',
'Main Notification Channel',
importance: Importance.max,
priority: Priority.high,
),
);
await notifPlugin.initialize(
InitializationSettings(
android: AndroidInitializationSettings('ic_launcher')),
onSelectNotification: (String? payload) async {
print('Inside on select Route Navigator. Route= $payload');
switch (payload!) {
case 'Home':
// navigatorKey is GlobalKey
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => Home()));
break;
case 'Auth':
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => Auth()));
break;
case 'Details':
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => UserDetails()));
break;
}
});
await notifPlugin.show(id, title, body, notificationDetails, payload:payload);
}
return Future.value(true);
}
}
When notification is clicked. Following message should print on console: "Inside on select Route Navigator. Route= Details" and he should be navigated on the UserDetails page but nothing seems to be happening.
I solved it by reacting to two different events.
If the App starts, i check if the app was launched by a notification. This can be done in createState or initState in main.dart. This code is useful for that.
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
String initialRoute = HomePage.routeName;
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
If the app is in background and a notification launches the app again, you must use Widgets Binding Observer and react to the App Resume event. There is an article at Medium which has example code for this case. Have a look at it here.
There is one drawback when reacting to App Resume Event and using the Flutter Local notifications Plugin. The aforementioned code always delivers true once triggered by an notification, even if the app entered background state again and was resumed manually by an user. This means code for changing a page will always be called, even
if you did not click an notification. Therefore, I´m using a boolean variable to trigger the App State Resume code once. Obviously, if you enter the app via notification ,the app gets resumed and you get a second notification, the code for changing a page will not be executed. It´s a workaround, but for my case, it´s good enough.
Related
I have an application, I want to send notifications only to authorized users,
authorization occurs by token if the token is empty, then the user is not authorized, for authorization and storing the token I use the shared_pref package. How can I disable notifications if the user is not logged in?
Token validation is only done in MyAppState notifications I need to disable in the same place
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await Permission.notification.request();
if(Constants.USER_TOKEN.isEmpty) {
FirebaseMessaging.instance.setAutoInitEnabled(false);
}
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
if (navKey.currentState != null) {
if(Constants.USER_TOKEN.isNotEmpty) {
navKey.currentState!.push(MaterialPageRoute(
builder: (context) => NotificationScreen()));
}
print('Message');
}
}
await _messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification!.android;
if (notification != null && android != null) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
icon: '#mipmap/ic_launcher',
),
),
);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage? message) {
print("On message opened app: ${message?.data}}");
if (navKey.currentState != null) {
if(Constants.USER_TOKEN.isNotEmpty) {
navKey.currentState!.push(MaterialPageRoute(
builder: (context) => const NotificationScreen()));
}
}
});
var initializationSettingsAndroid =
const AndroidInitializationSettings('#drawable/ic_notification');
var initializationSettingsIOS = const DarwinInitializationSettings();
var initializationSettings = InitializationSettings(
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
await _messaging.requestPermission();
String? token = await _messaging.getToken();
if (token != null) {
print("FIREBASE TOKEN $token");
} else {
print("CANNOT TAKE FIREBASE TOKEN");
}
if (Platform.isIOS) {
var APNS = await _messaging.getAPNSToken();
print('APNS: $APNS');
}
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
getToken() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
Constants.USER_TOKEN = pref.getString('login') ?? "";
});
}
#override
void didChangeDependencies() async {
super.didChangeDependencies();
await getToken();
}
Use deleteToken which invalidates the current token and getToken again to get a new one when enabling.
getToken() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
Constants.USER_TOKEN = pref.getString('login') ?? "";
});
if(Constants.USER_TOKEN.isEmpty()){
// delete firebase token
}else{
// get firebase token
}
}
i have implemented firebase push notification in my project with payload json,
I have done all the configurations and now when i click on the notification, it works sometimes perfectly but sometimes the redirection just doesn't work, let's say if i click the notification 10 times, it redirects to the desired page for 4 times and rest 6 times, its taking me to home screen. I am sharing my code below.
Main File
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();
await Firebase.initializeApp();
await SharedPref.initialize();
initilizeCamera();
initializeDb();
FlutterError.onError = (error) {
FirebaseCrashlytics.instance.recordFlutterFatalError(error);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
runApp(
const ProviderScope(child: MyApp()),
);
});
await pushNotificationToken();
/// Get initial message
await FirebaseMessaging.instance.getInitialMessage();
notificationActions(
action: (RemoteMessage? message) {
if (kDebugMode) {
if (message?.notification != null) {
}
}
if (message != null) {
notificationAction(message: message);
}
},
localNotification: true,
localNotificationAction: (payload) {
if (kDebugMode) {
}
notificationAction(payload: payload);
});
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.white,
statusBarColor: Colors.white,
));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return KeyboardDismissOnTap(
child: GetMaterialApp(
theme: appThemeData,
debugShowCheckedModeBanner: false,
title: 'XYZ',
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/',
initialBinding:XYZBindings(),
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
'/': (context) => const XYZScreen(),
}),
);
}
}
Firebase Notification Code
/// Firebase messaging instance
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
/// Get firebase notification instance
FirebaseMessaging get firebaseMessagingInstance => _firebaseMessaging;
/// Firebase push notification token
Future<String?> pushNotificationToken() async =>
_firebaseMessaging.getToken().then((value) {
Log.w('FCM TOKEN :: $value');
return value;
});
/// When user clicks on the notification
void notificationActions({
#required Function(RemoteMessage?)? action,
bool localNotification = false,
Function(Map<String, dynamic>)? localNotificationAction,
}) {
/// Make sure localNotification is Not Null
if (localNotification) {
assert(localNotificationAction != null);
}
if (Platform.isIOS) {
_requestPermissions();
}
if (localNotification) {
FlutterLocalNotificationHelper().initializeSettings(
actionCallback: localNotificationAction,
);
}
/// Get initial message
_firebaseMessaging.getInitialMessage().then((RemoteMessage? message) {
action!(message);
});
/// On Message
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
if (localNotification) {
await FlutterLocalNotificationHelper().showNotificationWithDefaultSound(
title: message.notification?.title,
body: message.notification?.body,
payload: jsonEncode(message.data),
);
}
});
/// On Message open app
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
action!(message);
});
}
/// Request permission for iOS
void _requestPermissions() {
_firebaseMessaging.requestPermission(
provisional: true,
);
}
Local Notification Page
/// Helper class for local notifications
class FlutterLocalNotificationHelper {
/// Constructor
factory FlutterLocalNotificationHelper() => _instance;
FlutterLocalNotificationHelper._();
/// Flutter LocalNotification Helper instance
static final FlutterLocalNotificationHelper _instance =
FlutterLocalNotificationHelper._();
/// Flutter LocalNotification for iOS Category
static const String darwinNotificationCategory = 'plainCategory';
/// Notification plugin
late FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin;
/// Flutter LocalNotifications Plugin
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
/// Request local notification
Future<void> requestLocalNotification() async {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission();
}
/// local notification plugin callback
late Function(Map<String, dynamic>) _localNotificationCallback;
/// Initialize local notifications
Future<void> initializeSettings({
#required Function(Map<String, dynamic>)? actionCallback,
}) async {
if (actionCallback != null) {
_localNotificationCallback = actionCallback;
}
/// initialize settings for => Android
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/ic_launcher');
/// initialize settings for => iOS
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
);
/// Initialize settings for both => Android / iOS
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
/// Flutter local notification plugin
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Local notification plugin initialize
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
if (response.payload != null) {
_localNotificationCallback(json.decode(response.payload!));
}
},
);
/// On notification click
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await _flutterLocalNotificationsPlugin
.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
await onSelectNotification(
notificationAppLaunchDetails?.notificationResponse.toString(),
);
}
}
/// WHEN USER CLICKS TO NOTIFICATION
Future<void> onSelectNotification(String? payload) async {
if (payload != null) {
final Map<String, dynamic> payloadJson =
jsonDecode(payload) as Map<String, dynamic>;
_localNotificationCallback(payloadJson);
}
}
/// SHOW LOCAL NOTIFICATION
/// 'your channel id', 'your channel name', IS NOT NEEDED
Future<void> showNotificationWithDefaultSound({
#required String? title,
#required String? body,
#required String? payload,
}) async {
/// Android notification details
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'your channel id',
'your channel name',
importance: Importance.max,
priority: Priority.high,
color: AppColors.red,
icon: '#mipmap/ic_launcher',
);
/// iOS Notification details
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
DarwinNotificationDetails(
categoryIdentifier: darwinNotificationCategory,
presentSound: true,
presentAlert: true,
badgeNumber: 0,
);
/// Notification platforms details
const NotificationDetails platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
/// Show notification
await _flutterLocalNotificationsPlugin.show(
0,
title,
body,
platformChannelSpecifics,
payload: payload,
);
}
}
Notifications Action page
void notificationAction(
{RemoteMessage? message, Map<String, dynamic>? payload}) {
The redirection is written here.
}
please let me know that why sometimes the redirection works and sometimes not.
I use Firebase messaging in my Flutter app , I want to navigate to another screen when I click on the notification even my app is in foreground or background , I used many functions and it doesn't trigger the click event and I can't find anything can solve my problem .
When I click on the notification when app is in foreground or background , nothing happened because it navigate to the same page .
And when I click on the notification when app is terminated , it opens on Splash screen and go to the home not the screen that I want .
I added this intent-filter in my Manifest
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
And I added this to the Json object
"click_action": "FLUTTER_NOTIFICATION_CLICK",
And here is how can I get background FCM in the main.dart
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance', // id
'High Importance Notifications', // title
importance: Importance.high,
playSound: true);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
SessionManager sessionManager = SessionManager();
await Firebase.initializeApp();
//final sound = 'sound.mp3';
print('A bg message just showed up : ${message.messageId}');
final android = AndroidInitializationSettings('#mipmap/ic_launcher');
final ios = IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,);
final settings = InitializationSettings(android: android,iOS: ios);
flutterLocalNotificationsPlugin.initialize(settings,);
if(message.data['title'].toString().toLowerCase()=="new request") {
sessionManager.getBadge().then((badge) {
if (badge != null) {
int x = badge + 1;
sessionManager.saveBadge(x);
print("notification number is " + x.toString());
}
else {
sessionManager.saveBadge(1);
}
});
}
flutterLocalNotificationsPlugin.show(
message.data.hashCode,
message.data['title'],
message.data['body'],
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
importance: Importance.high,
priority: Priority.high,
// sound: RawResourceAndroidNotificationSound(sound.split('.').first),
playSound: true,
icon: '#mipmap/ic_launcher',
),
));
/*NotificationApi.showNotification(
title: message.data['title'],
body: message.data['body'],
payload: "",
id: int.parse(channel.id));*/
}
Future<void> main() async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
runApp(MyApps());
// configLoading();
}
class MyApps extends StatefulWidget {
const MyApps({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return MyApp();
}
}
class MyApp extends State<MyApps> {
static ValueNotifier<int> strikeNotifier = ValueNotifier(0);
Color _primaryColor = Color(0xff0d8b75);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return ScreenUtilInit(
builder: () => MaterialApp(
debugShowCheckedModeBanner: false,
home: SplashScreen(),
),
designSize: const Size(1080, 2280),
);
}
void showNotification(String title, String body) async {
await _demoNotification(title, body);
}
Future<void> _demoNotification(String title, String body) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'channel_I', 'channel name',
showProgress: true,
priority: Priority.high,
playSound: true,
ticker: 'test ticker');
var iOSChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics, iOS: iOSChannelSpecifics);
await flutterLocalNotificationsPlugin
.show(0, title, body, platformChannelSpecifics, payload: 'test');
}
#override
void initState() {
super.initState();
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
if (message != null) {
Navigator.push(context, MaterialPageRoute(builder: (context)=>DoneAndPaiedPagess(0)));
}
});
getToken().then((value) {
if(value!=null) {
AppConstants.firebaseToken = value;
}
});
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
new FlutterLocalNotificationsPlugin();
var initializationSettingsAndroid = AndroidInitializationSettings('#mipmap/ic_launcher');
var initializationSettingsIOS = IOSInitializationSettings();
var initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
var data = message.data;
// AndroidNotification? android = message.notification?.android;/
if (data != null ) {
if(data['title'].toString().toLowerCase()=="new request") {
SessionManager sessionManager = SessionManager(context);
sessionManager.getBadge().then((badge) {
if (badge != null) {
setState(() {
int x = badge + 1;
strikeNotifier.value = x;
sessionManager.saveBadge(x);
});
}
else {
strikeNotifier.value = 1;
sessionManager.saveBadge(1);
}
});
}
print("entered");
flutterLocalNotificationsPlugin.show(
data.hashCode,
data['title'],
data['body'],
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
playSound: true,
icon: '#mipmap/ic_launcher',
),
));
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
Navigator.push(context, MaterialPageRoute(builder: (context)=>DoneAndPaiedPagess(0)));
});
}
Future<String?> getToken() async{
String? token = await FirebaseMessaging.instance.getToken();
print("token is "+token!);
return token;
}
}
In yaml
firebase_core: ^1.12.0
firebase_messaging: ^11.2.6
dependency_overrides:
firebase_messaging_platform_interface: 3.1.6
Edit : from multiple solutions , I tried the most common solution that I used onMessageOpenedApp in initState but it doesn't enter in it
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
Navigator.push(context, MaterialPageRoute(builder: (context)=>DoneAndPaiedPagess(0)));
});
In your code, you are using the flutter_local_notifications plugin and you are creating local notification when you get a push notification from firebase messaging.
Since the notification is not created by firebase messaging so, you are not getting on tap callback in getInitialMessage and onMessageOpenedApp.
In order to get on tap callback on local notifications, you can pass a callback function while initializing flutter_local_notifications
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
void selectNotification(String payload) async {
if (payload != null) {
debugPrint('notification payload: $payload');
// Here you can check notification payload and redirect user to the respective screen
await Navigator.push(
context,
MaterialPageRoute<void>(builder: (context) => SecondScreen(payload)),
);
}
}
For more, you can check flutter_local_notifications documentation
On your home widget within initState, check the getInitialMessage value :
// get the remote message when your app opened from push notification while in background state
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
// check if it is exists
if (initialMessage != null) {
// check the data property within RemoteMessage and do navigate based on it
}
Check the firebase flutter documentation here https://firebase.flutter.dev/docs/messaging/notifications/#handling-interaction.
Use onMessageOpenedApp stream to listen when a notification is opened in the foreground or background. When the application is terminated, use getInitialMessage to get a pending notification.
I am using fluttter_local_notifications, I initialized the plugins as per the instructions on the docs and it looks like this
var initializationSettingsAndroid = AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
onDidReceiveLocalNotification:
(int id, String title, String body, String payload) async {
didReceiveLocalNotificationSubject.add(ReceivedNotification(
id: id, title: title, body: body, payload: payload));
});
var initializationSettings = InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification:(String payload) async {// this fires when notification is clicked
if (payload != null) {
debugPrint('notification payload: ' + payload);
}
//this line below is not working but prints the payload fine
await Navigator.push(mcontext, new MaterialPageRoute(builder: (context) => new SecondScreen(payload)),);
});
The navigator inside onSelectNotifications isn't doing anyting, the line after that is never executed.
I faced the same problem, nothing logged and noting happens, here is a work around that worked fine with me:
instead of using context to navigate use GlobalKey so replace:
Navigator.push(mcontext, new MaterialPageRoute(builder: (context) => new SecondScreen(payload)),);
with
navigatorKey.currentState.push(mcontext, new MaterialPageRoute(builder: (context) => new SecondScreen(payload)),);
but first you have to declare this key:
final GlobalKey<NavigatorState> navigatorKey = GlobalKey(debugLabel: "Main Navigator");
and assign it to your app widget like that:
MaterialApp(
navigatorKey: navigatorKey,
....
Programmatically generated dynamic links are not properly catched by
FirebaseDynamicLinks.instance.getInitialLink().
if the app is closed. However, if the app is open it is properly detected by the listener for new incoming dynamic links. It is not clear to me if it is a setup problem, how I generate the dynamic link.
To Reproduce
First set up Firebase for Flutter project as documented. Then to set up a dynamic link:
/// See also
/// https://firebase.google.com/docs/dynamic-links/use-cases/rewarded-referral
/// how to implement referral schemes using Firebase.
Future<ShortDynamicLink> buildDynamicLink(String userId) async {
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
final String packageName = packageInfo.packageName;
var androidParams = AndroidParameters(
packageName: packageInfo.packageName,
minimumVersion: Constants.androidVersion, // app version and not the Android OS version
);
var iosParams = IosParameters(
bundleId: packageInfo.packageName,
minimumVersion: Constants.iosVersion, // app version and not the iOS version
appStoreId: Constants.iosAppStoreId,
);
var socialMetaTagParams = SocialMetaTagParameters(
title: 'Referral Link',
description: 'Referred app signup',
);
var dynamicLinkParams = DynamicLinkParameters(
uriPrefix: 'https://xxxxxx.page.link',
link: Uri.parse('https://www.xxxxxxxxx${Constants.referralLinkPath}?${Constants.referralLinkParam}=$userId'),
androidParameters: androidParams,
iosParameters: iosParams,
socialMetaTagParameters: socialMetaTagParams,
);
return dynamicLinkParams.buildShortLink();
}
This dynamic link then can be shared with other new users.
I listen for initial links at app startup and then for new incoming links.
1) The link properly opens the app if the app is not running but the getInitialLink does not get it.
2) If the app is open the link is properly caught by the listener and all works.
Here is the very simple main.dart that I used to verify 1) that the initial link is not found with FirebaseDynamicLinks.instance.getInitialLink().
void main() async {
WidgetsFlutterBinding.ensureInitialized();
PendingDynamicLinkData linkData = await FirebaseDynamicLinks.instance.getInitialLink();
String link = linkData?.link.toString();
runApp(MyTestApp(link: link));
}
class MyTestApp extends StatelessWidget {
final String link;
MyTestApp({this.link});
#override
Widget build(BuildContext context) {
return MaterialApp(
builder: (BuildContext context, Widget child) {
return Scaffold(
body: Container(
child: Center(
child: Text('Initial dynamic Firebase link: $link')
),
),
);
}
);
}
}
Expected behavior
The link should open the app and trigger FirebaseDynamicLinks.instance.getInitialLink()..
Additional context
I hope properly configured Firebase project with Firebase console. To verify this I created a dynamic link to be used with Firebase Auth 'signup by email link' and these dynamic links are working as expected, also when the app is not open.
The point here is that the referral dynamic link that I generate programmatically is opening the app when it is closed but is then not caught by FirebaseDynamicLinks.instance.getInitialLink(), and to make things more confusing, works as expected if the app is open. In that case it is caught by the listener FirebaseDynamicLinks.instance.onLink.
I also set up the WidgetsBindingObserver in Flutter to handle that callback as required, when the app gets its focus back.
Any help is greatly appreciated. Debugging is very tricky, as you need to do it on a real device and not in the simulator. To make things worse, I did not figure out how to attach a debugger while the dynamic link opens the app. This means I am also stuck in investigating this issue further.
In The FirebaseDynamicLinks Two Methods 1) getInitialLink() 2) onLink().
If When Your App Is Open And You Click On Dynamic Link Then Will Be Call FirebaseDynamicLinks.instance.onLink(), If Your App Is Killed Or Open From PlayStore Then You Get From FirebaseDynamicLinks.instance.getInitialLink();.
First Of You Need To Initialise Instance Of FirebaseDynamicLinks.instance.
static void initDynamicLinks() async {
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
if (deepLink != null && deepLink.queryParameters != null) {
SharedPrefs.setValue("param", deepLink.queryParameters["param"]);
}
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
final Uri deepLink = dynamicLink?.link;
if (deepLink != null && deepLink.queryParameters != null) {
SharedPrefs.setValue("param", deepLink.queryParameters["param]);
}
}, onError: (OnLinkErrorException e) async {
print(e.message);
});
}
Initialize Link Listener. This works for me.
class _MainAppState extends State<MainApp> {
Future<void> initDynamicLinks() async {
print("Initial DynamicLinks");
FirebaseDynamicLinks dynamicLinks = FirebaseDynamicLinks.instance;
// Incoming Links Listener
dynamicLinks.onLink.listen((dynamicLinkData) {
final Uri uri = dynamicLinkData.link;
final queryParams = uri.queryParameters;
if (queryParams.isNotEmpty) {
print("Incoming Link :" + uri.toString());
// your code here
} else {
print("No Current Links");
// your code here
}
});
// Search for Firebase Dynamic Links
PendingDynamicLinkData? data = await dynamicLinks
.getDynamicLink(Uri.parse("https://yousite.page.link/refcode"));
final Uri uri = data!.link;
if (uri != null) {
print("Found The Searched Link: " + uri.toString());
// your code here
} else {
print("Search Link Not Found");
// your code here
}
}
Future<void> initFirebase() async {
print("Initial Firebase");
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// await Future.delayed(Duration(seconds: 3));
initDynamicLinks();
}
#override
initState() {
print("INITSTATE to INITIALIZE FIREBASE");
super.initState();
initFirebase();
}
I tried Rohit's answer and because several people face the same issue I add here some more details. I created a stateful widget that I place pretty much at the top of the widget tree just under material app:
class DynamicLinkWidget extends StatefulWidget {
final Widget child;
DynamicLinkWidget({this.child});
#override
State<StatefulWidget> createState() => DynamicLinkWidgetState();
}
class DynamicLinkWidgetState extends State<DynamicLinkWidget> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
locator.get<DynamicLinkService>().initDynamicLinks();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(child: widget.child);
}
}
I use the getit package to inject services. The dynamic link service is roughly like this:
class DynamicLinkService {
final UserDataService userDataService;
final ValueNotifier<bool> isLoading = ValueNotifier<bool>(false);
final BehaviorSubject<DynamicLinkError> _errorController = BehaviorSubject<DynamicLinkError>();
Stream<DynamicLinkError> get errorStream => _errorController.stream;
DynamicLinkService({#required this.userDataService});
void initDynamicLinks() async {
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
if (deepLink != null) {
processDynamicLink(deepLink);
}
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
final Uri deepLink = dynamicLink?.link;
if (deepLink != null) {
print('=====> incoming deep link: <${deepLink.toString()}>');
processDynamicLink(deepLink);
}
},
onError: (OnLinkErrorException error) async {
throw PlatformException(
code: error.code,
message: error.message,
details: error.details,
);
}
);
}
Future<void> processDynamicLink(Uri deepLink) async {
if (deepLink.path == Constants.referralLinkPath && deepLink.queryParameters.containsKey(Constants.referrerLinkParam)) {
var referrer = referrerFromDynamicLink(deepLink);
userDataService.processReferrer(referrer);
} else {
await FirebaseEmailSignIn.processDynamicLink(
deepLink: deepLink,
isLoading: isLoading,
onError: this.onError
);
}
}
void onError(DynamicLinkError error) {
_errorController.add(error);
}
}
You see that my app has to process two types of dynamic link, one is for email link signup, the other link is our referral link that is used to link users together and allow us to understand who introduced a new user to us. This setup works now for us. Hope it helps others too.