I am working on implementing FCM in my app. If I send a notification from Firebase Console it works, but if I try to send a message it does not go through.
(Also on a side note, in Firebase Console I can see that 2 notifications sent and I clicked on them but it shows 0 clicks)
Here is my code:
notification_service.dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationService {
static final _messaging = FirebaseMessaging.instance;
static String? _token;
static final FlutterLocalNotificationsPlugin
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static const AndroidNotificationChannel _androidNotificationChannel =
AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for important notifications.',
importance: Importance.max,
);
static const DarwinNotificationDetails _iOSNotificationChannel =
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
static void _requestPermission() async {
final settings = await _messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
} else if (settings.authorizationStatus ==
AuthorizationStatus.provisional) {
} else {}
}
static void _getFCMToken() async {
_token = await _messaging.getToken();
_messaging.onTokenRefresh.listen((newValue) {
_token = newValue;
});
debugPrint('FCM Token: $_token');
}
static void _configureLocalNotificationPlugin() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
);
await _flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
),
onDidReceiveNotificationResponse: (response) {},
);
await _messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
static void _createAndroidNotificationChannel() async {
await _flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(_androidNotificationChannel);
}
static void _showForegroundNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
if (notification != null) {
_flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
_androidNotificationChannel.id,
_androidNotificationChannel.name,
channelDescription: _androidNotificationChannel.description,
icon: 'launch_background',
),
iOS: _iOSNotificationChannel,
),
);
}
}
static void _handleBackgroundNotificationOnTap(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
if (notification != null) {
debugPrint('Notification clicked: ${notification.title}');
clearAllNotifications();
}
}
static void clearAllNotifications() async {
await _flutterLocalNotificationsPlugin.cancelAll();
}
static void _handleInAppMessage(BuildContext context, RemoteMessage message) {
if (message.notification != null) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(message.notification!.title ?? 'No Title'),
content: Text(message.notification!.body ?? 'No Body'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('OK'),
),
],
);
},
);
}
}
static Future<void> init(BuildContext context,
{required void Function(RemoteMessage message) onMessage}) async {
_requestPermission();
_getFCMToken();
_configureLocalNotificationPlugin();
_createAndroidNotificationChannel();
FirebaseMessaging.onMessage.listen((message) {
_showForegroundNotification(message);
_handleInAppMessage(context, message);
});
FirebaseMessaging.onMessageOpenedApp
.listen(_handleBackgroundNotificationOnTap);
}
static Future<void> setForegroundNotificationPresentationOptions({
bool alert = true,
bool badge = true,
bool sound = true,
}) {
return _messaging.setForegroundNotificationPresentationOptions(
alert: alert,
badge: badge,
sound: sound,
);
}
}
Main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:manebenefitsstudent/constants/routes.dart';
import 'package:manebenefitsstudent/constants/theme_data.dart';
import 'package:manebenefitsstudent/services/auth/bloc/auth_bloc.dart';
import 'package:manebenefitsstudent/services/auth/bloc/auth_event.dart';
import 'package:manebenefitsstudent/services/auth/bloc/auth_state.dart';
import 'package:manebenefitsstudent/services/auth/firebase_auth_provider.dart';
import 'package:manebenefitsstudent/services/database/data_initializer.dart';
import 'package:manebenefitsstudent/services/database/data_provider.dart';
import 'package:manebenefitsstudent/utilities/logging/firebase_analytics_logging.dart';
import 'package:manebenefitsstudent/views/categories/activities_view.dart';
import 'package:manebenefitsstudent/views/categories/categories_home_view.dart';
import 'package:manebenefitsstudent/views/categories/health_and_beauty_view.dart';
import 'package:manebenefitsstudent/views/categories/restaurants_view.dart';
import 'package:manebenefitsstudent/views/categories/retail_view.dart';
import 'package:manebenefitsstudent/views/categories/services_view.dart';
import 'package:manebenefitsstudent/views/categories/seven_points_view.dart';
import 'package:manebenefitsstudent/views/settings_view.dart';
import 'package:manebenefitsstudent/views/login_view.dart';
import 'package:manebenefitsstudent/views/register_view.dart';
import 'package:manebenefitsstudent/views/forgot_password_view.dart';
import 'package:manebenefitsstudent/views/verify_email_view.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:manebenefitsstudent/widgets/reusable/loading/loading_screen.dart';
import 'package:manebenefitsstudent/widgets/reusable/loading/splash_screen.dart';
import 'package:provider/provider.dart';
import 'package:upgrader/upgrader.dart';
import 'firebase_options.dart';
import 'services/auth/auth_provider.dart';
import 'services/firebase_messaging/notification_service.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await _firebaseInitialization();
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await _firebaseInitialization();
await dotenv.load(fileName: ".env");
await DataInitializer.initializeData();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => DataProvider()),
Provider<AuthProvider>(
create: (_) => FirebaseAuthProvider()..initialize()),
BlocProvider(
create: (context) => AuthBloc(FirebaseAuthProvider()),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
NotificationService.init(context, onMessage: (RemoteMessage message) {});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
NotificationService.clearAllNotifications();
}
}
@override
Widget build(BuildContext context) {
return UpgradeAlert(
dialogStyle: UpgradeDialogStyle.cupertino,
child: MaterialApp(
title: 'Mane Benefits: Alumni & Friends',
debugShowCheckedModeBanner: false,
theme: AppTheme.theme,
home: const SplashScreen(),
routes: {
activitiesViewRoute: (context) => const ActivitiesView(),
healthAndBeautyViewRoute: (context) => const HealthAndBeautyView(),
restaurantsViewRoute: (context) => const RestaurantsView(),
retailViewRoute: (context) => const RetailView(),
servicesViewRoute: (context) => const ServicesView(),
sevenPointsViewRoute: (context) => const SevenPointsView(),
settingsViewRoute: (context) => const SettingsView(),
},
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
context.read<AuthBloc>().add(const AuthEventInitialize());
return BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state.isLoading) {
if (!Navigator.of(context).canPop()) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const LoadingScreen(loadingText: 'Loading...'),
),
);
}
} else {
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
}
},
builder: (context, state) {
if (state is AuthStateLoggedIn) {
FirebaseAnalyticsService.logStateChange('AuthStateLoggedIn');
return const CategoriesHomeView();
} else if (state is AuthStateNeedsVerification) {
FirebaseAnalyticsService.logStateChange('AuthStateNeedsVerification');
return const VerifyEmailView();
} else if (state is AuthStateLoggedOut) {
FirebaseAnalyticsService.logStateChange('AuthStateLoggedOut');
return const LoginView();
} else if (state is AuthStateForgotPassword) {
FirebaseAnalyticsService.logStateChange('ForgotPasswordView');
return const ForgotPasswordView();
} else if (state is AuthStateRegistering) {
FirebaseAnalyticsService.logStateChange('AuthStateRegistering');
return const RegisterView();
} else {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
},
);
}
}
Future<void> _firebaseInitialization() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
}
for IOS:
AppDelegate.swift
import Flutter
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func applicationDidBecomeActive(_ application: UIApplication) {
application.applicationIconBadgeNumber = 0;
}
}
For Android:
MainActivity.kt
package org.manebenefits.manebenefitsstudent
import android.os.Bundle
import android.util.Log
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val type = intent?.getStringExtra("type")
if (type != null) {
Log.d("MainActivity", "Notification clicked with type: $type")
}
}
}
AndroidManifst.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:label="MB: Student"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:enableOnBackInvokedCallback="true">
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@mipmap/ic_launcher" />
<service
android:name=".firebase.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="io.flutter.plugins.urllauncher.WebViewActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:exported="false"
tools:replace="android:exported">
<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="http"/>
<data android:scheme="https"/>
</intent-filter>
</activity>
<receiver
android:name="com.dexterous.flutterlocalnotifications.receivers.ActionBroadcastReceiver"
android:exported="true"/>
<receiver
android:name="com.dexterous.flutterlocalnotifications.receivers.DismissedReceiver"
android:exported="true"/>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
</manifest>
I am still learning flutter so this is all pretty new to me.