Hi everyone today I will write an article about Push Notification in a Flutter mobile app. Before going into the tutorial I will remind you a little about Push Notification to remind everyone to remember .
What is Push Notification?
Push notifications are messages sent by the server to the client through a cloud message gateway and from this port will notify our computer that there are new notifications and display them. Therefore, it is required to have internet for push notification.
Why use Push Notification
- Usually a notification is automatically enabled to notify the user that the application has completed a certain job. Or you can send promotional information to your customers, invite customers to attend an event … –
- When building a mobile application, push notification is a function that must not be ignored, it will make your application become (better).
This is briefly followed by the main part of this article, everyone learn offline.
Install Push Notification
Because Flutter is a cross-platform framework, we will have to configure on both Android and iOS files and here I only configure on mobile.
Before installing Push Notification, everyone must link to Firebase to do this part.
Reference link: https://viblo.asia/p/lien-ket-firebase-den-app-flutter-cho-android-va-ios-RnB5p6zdZPG
General settings
Everyone goes to pubspect.yaml file and adds the code below
1 2 3 4 5 6 7 8 9 10 11 | dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 firebase_analytics: any firebase_messaging: 6.0.9 firebase_core: ^0.4.0 |
Then type in command line packages get
to sync the config file again.
Install on Android
You go to the file android/build.gradle
add the following:
1 2 3 4 5 | dependencies { classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.google.gms:google-services:4.3.3' } |
In the path to android/app/build.gradle
apply plugin below
1 2 | apply plugin: 'com.google.gms.google-services' |
To be able to handle background messages, Google recommends adding new versions.
1 2 3 4 5 6 7 | dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' implementation 'com.google.firebase:firebase-messaging:20.1.0' } |
Go into AndroidManifest.xml
and add an intent as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <application ...> <activity android:name=".MainActivity" ...> ... <intent-filter> <action android:name="FLUTTER_NOTIFICATION_CLICK" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> ... </application> |
Create an Application.java
or Application.kt
class (if everyone uses Kotlin) in a folder containing MainActivity
with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class Application extends FlutterApplication implements PluginRegistrantCallback { @Override public void onCreate() { super.onCreate(); FlutterFirebaseMessagingService.setPluginRegistrant(this); } @Override public void registerWith(PluginRegistry registry) { FirebaseCloudMessagingPluginRegistrant.registerWith(registry); } } |
Create another FirebaseCloudMessagingPluginRegistrant
class in the same directory as Application.java
with the following content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin; public final class FirebaseCloudMessagingPluginRegistrant{ public static void registerWith(PluginRegistry registry) { if (alreadyRegisteredWith(registry)) { return; } FirebaseMessagingPlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")); } private static boolean alreadyRegisteredWith(PluginRegistry registry) { final String key = FirebaseCloudMessagingPluginRegistrant.class.getCanonicalName(); if (registry.hasPlugin(key)) { return true; } registry.registrarFor(key); return false; } } |
In the MainActivity.java
file, comment its content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package com.djamware.myflutter; //import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; //import io.flutter.embedding.engine.FlutterEngine; //import io.flutter.plugins.GeneratedPluginRegistrant; public class MainActivity extends FlutterActivity { /* @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); } } */ |
Finally, go to AndroidManifest.xml
and change the name of the <application>
tag as follows
1 2 3 4 5 | <application android:name=".Application" ... </application> |
That's it, the installation steps for Android, now let's go to iOS.
Install on iOS
Open the ios/Runner/AppDelegate.m
or the ios/Runner/AppDelegate.swift
and add the following line inside (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (NSDictionary *) launchOptions method.
1 2 3 4 | if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } |
Code Demo
To make it easy, I will code on the main.dart
file. I will create a method to be able to receive notifications when the app runs in the background.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import 'dart:async'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) { if (message.containsKey('data')) { final dynamic data = message['data']; } if (message.containsKey('notification')) { final dynamic notification = message['notification']; } } |
Next create a Map<String, Items>
to be able to map with the JSON string that FCM returns for me.
1 2 3 4 5 6 7 8 9 10 | final Map<String, Item> _items = <String, Item>{}; Item _itemForMessage(Map<String, dynamic> message) { final dynamic data = message['data'] ?? message; final String itemId = data['id']; final Item item = _items.putIfAbsent(itemId, () => Item(itemId: itemId)) .._matchteam = data['matchteam'] .._score = data['score']; return item; } |
Create a Widget to display the details of the message received
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | class DetailPage extends StatefulWidget { DetailPage(this.itemId); final String itemId; @override _DetailPageState createState() => _DetailPageState(); } class _DetailPageState extends State<DetailPage> { Item _item; StreamSubscription<Item> _subscription; @override void initState() { super.initState(); _item = _items[widget.itemId]; _subscription = _item.onChanged.listen((Item item) { if (!mounted) { _subscription.cancel(); } else { setState(() { _item = item; }); } }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text("Match ID ${_item.itemId}"), ), body: SingleChildScrollView( child: Container( padding: EdgeInsets.all(20.0), child: Card( child: Container( padding: EdgeInsets.all(10.0), child: Column( children: <Widget>[ Container( margin: EdgeInsets.fromLTRB(0, 0, 0, 10), child: Column( children: <Widget>[ Text('Today match:', style: TextStyle(color: Colors.black.withOpacity(0.8))), Text( _item.matchteam, style: Theme.of(context).textTheme.title) ], ), ), Container( margin: EdgeInsets.fromLTRB(0, 0, 0, 10), child: Column( children: <Widget>[ Text('Score:', style: TextStyle(color: Colors.black.withOpacity(0.8))), Text( _item.score, style: Theme.of(context).textTheme.title) ], ), ), ], ) ), ), ), ), // This trailing comma makes auto-formatting nicer for build methods. ); } } |
Edit a little bit about Homepage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { bool _topicButtonsDisabled = false; final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); final TextEditingController _topicController = TextEditingController(text: 'topic'); Widget _buildDialog(BuildContext context, Item item) { return AlertDialog( content: Text("${item.matchteam} with score: ${item.score}"), actions: <Widget>[ FlatButton( child: const Text('CLOSE'), onPressed: () { Navigator.pop(context, false); }, ), FlatButton( child: const Text('SHOW'), onPressed: () { Navigator.pop(context, true); }, ), ], ); } void _showItemDialog(Map<String, dynamic> message) { showDialog<bool>( context: context, builder: (_) => _buildDialog(context, _itemForMessage(message)), ).then((bool shouldNavigate) { if (shouldNavigate == true) { _navigateToItemDetail(message); } }); } void _navigateToItemDetail(Map<String, dynamic> message) { final Item item = _itemForMessage(message); // Clear away dialogs Navigator.popUntil(context, (Route<dynamic> route) => route is PageRoute); if (!item.route.isCurrent) { Navigator.push(context, item.route); } } @override void initState() { super.initState(); _firebaseMessaging.configure( onMessage: (Map<String, dynamic> message) async { print("onMessage: $message"); _showItemDialog(message); }, onBackgroundMessage: myBackgroundMessageHandler, onLaunch: (Map<String, dynamic> message) async { print("onLaunch: $message"); _navigateToItemDetail(message); }, onResume: (Map<String, dynamic> message) async { print("onResume: $message"); _navigateToItemDetail(message); }, ); _firebaseMessaging.requestNotificationPermissions( const IosNotificationSettings( sound: true, badge: true, alert: true, provisional: true)); _firebaseMessaging.onIosSettingsRegistered .listen((IosNotificationSettings settings) { print("Settings registered: $settings"); }); _firebaseMessaging.getToken().then((String token) { assert(token != null); print("Push Messaging token: $token"); }); _firebaseMessaging.subscribeToTopic("matchscore"); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('My Flutter FCM'), ), body: SingleChildScrollView( child: Container( padding: EdgeInsets.all(20.0), child: Card( child: Container( padding: EdgeInsets.all(10.0), child: Column( children: <Widget>[ Container( margin: EdgeInsets.fromLTRB(0, 0, 0, 10), child: Column( children: <Widget>[ Text('Welcome to this Flutter App:', style: TextStyle(color: Colors.black.withOpacity(0.8))), Text('You already subscribe to the matchscore topic', style: Theme.of(context).textTheme.title) ], ), ), Container( margin: EdgeInsets.fromLTRB(0, 0, 0, 10), child: Column( children: <Widget>[ Text('Now you will receive the push notification from the matchscore topics', style: TextStyle(color: Colors.black.withOpacity(0.8))) ], ), ), ], ) ), ), ), ), ); } } Finally, modify the main method to be like this. void main() { runApp( MaterialApp( home: MyHomePage(), ), ); } |
Conduct build and test. On the Home screen will be as follows:
Upon receiving the data, it will appear on the dialog.
And when the app runs in the background, it will display a notice outside the screen.
Thanks for watching.
Reference links :
Github: https://github.com/didinj/flutter-firebase-cloud-messaging-fcm-push-notification-example.git