'How to get a full screen notification from FCM?
I am building a home automation project that has a fire sensor that will write to Firebase Database if there is a fire detected, then from that point I need to make an alarm for the user. I managed to trigger a notification from Firebase cloud functions, but that's not exactly what I want. What I want is to make a full-screen notification to the user with a custom sound something like a phone alarm or a what's app call when there is a fire alarm -change in the database-.
I tried as a Top level function with no error while running my application:
firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print('onMessage: $message');
toast3('asdasdsawwwww $message');
setMessage(message);
},
onLaunch: (Map<String, dynamic> message) async {
print('onLaunch: $message');
setMessage(message);
},
onResume: (Map<String, dynamic> message) async {
print('onResume: $message');
setMessage(message);
},
onBackgroundMessage: myBackgroundMessageHandler);
print('onMessage:12qew11');
firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true),
);
}
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
print('HEREE');
final assetsAudioPlayer = AssetsAudioPlayer();
assetsAudioPlayer.open(
Audio("assets/audio/alarm.mp3"),
);
return Fluttertoast.showToast(
msg: 'done background:))))$message',
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIos: 4,
backgroundColor: Colors.redAccent,
textColor: Colors.white,
fontSize: 15.0);
}
My Firebase function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().functions);
var fireDatabase;
exports.myFirstCloudFun = functions.database.ref('/usersData/{userID}/Fire').onUpdate(async (event, context) => {
const uidGotten = context.params.userID;
const fireData = event.after.val()
console.log('data changed in fire is' + fireData + 'userID is ' + uidGotten);
const usereIdTokens = await admin
.firestore()
.collection(uidGotten)
.doc('userTokens')
.get();
console.log('Tokens to try are' + usereIdTokens.data);
var tokens = usereIdTokens.data().user_all_tokens;
var payload = {
notification: {
title: 'Push Title',
body: 'Push Body' + fireData,
sound: 'default',
},
data: {
push_key: 'Fire Value Is',
key1: "fireData is " + fireData,
},
};
tokens.forEach.toString().trim;
console.log('Tokens to send are ' + tokens[1] + ' ////// ' + tokens);
try {
const response = await admin.messaging().sendToDevice(tokens, payload);
console.log('Notification sent successfully');
} catch (err) {
console.log(err);
}
});
My Application.kt
package com.eghubs.eg_home_hubs
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService
public class Application: FlutterApplication(), PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
FlutterFirebaseMessagingService.setPluginRegistrant(this)
}
override fun registerWith(registry: PluginRegistry) {
FirebaseCloudMessagingPluginRegistrant.registerWith(registry)
}
}
My FirebaseCloudMessagingPluginRegistrant.kt
package com.eghubs.eg_home_hubs
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin
class FirebaseCloudMessagingPluginRegistrant {
companion object {
fun registerWith(registry: PluginRegistry) {
if (alreadyRegisteredWith(registry)) {
return;
}
FirebaseMessagingPlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"))
}
fun alreadyRegisteredWith(registry: PluginRegistry): Boolean {
val key = FirebaseCloudMessagingPluginRegistrant::class.java.name
if (registry.hasPlugin(key)) {
return true
}
registry.registrarFor(key)
return false
}
}
}
My AndroidManifest is :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.eghubs.eg_home_hubs">
<uses-permission android:name="android.permission.ACCESS_CORSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here.
android:name="io.flutter.app.FlutterApplication"
android:name="androidx.multidex.MultiDexApplication"
-->
<application
android:name=".Application" <!-- here is the change-->
android:label="EG HomeHubs"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher">
tools:replace="android:allowBackup">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:showWhenLocked="true"
android:turnScreenOn="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter> <!-- Noti:this is for cloud messiging -->
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- 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>
</manifest>
but I get nothing when the application is not opened in the background, why onBackgroundMessage function is not called?
My question is how can I achieve that what's an app call or something of that kind?
OR Is there any better way to do this, some other way to achieve that fire alarm functionality in my project from the firebase database change?
EDIT: My app/build.gradle
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
//GradleException
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.eghubs.eg_home_hubs"
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
// multiDexEnabled true
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
debug {
minifyEnabled true
shrinkResources true
}
}
}
flutter {
source '../..'
}
dependencies {
implementation 'com.google.firebase:firebase-analytics'
implementation platform('com.google.firebase:firebase-bom:26.0.0')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// implementation 'androidx.multidex:multidex:2.0.1' //with androidx libraries
implementation'com.google.firebase:firebase-messaging:21.0.1'
}
apply plugin: 'com.android.application'
// Add this line
apply plugin: 'com.google.gms.google-services'
My android/build.gradle:
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.google.gms:google-services:4.3.4'
//classpath 'com.android.tools.build:gradle:3.5.3' //todo rollback this
classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Solution 1:[1]
Alright.
Since update 8.0.0-dev.1 we can get onBackgroundMessage by default without any need for these installation process
NEW: FirebaseMessaging.onBackgroundMessage() Sets a background message handler to trigger when the app is in the background or terminated.
Solution 2:[2]
I have some solutions for solving this problem.
Solution 1:
The first thing you have to do is to check if onBackgroundMessage
is supported in iOS or not.
And in Android, you have to turn on the Allow running in background
option.
Solution 2:
Create a high importance channel through the flutter_local_notifications
package.
flutter_local_notifications package link: https://pub.dev/packages/flutter_local_notifications
Solution 3:
- Create a new file
App.java
inside folder java/com/yourdomain
package com.yourdomain;
import io.flutter.app.FlutterApplication;
public class App extends FlutterApplication {
@Override
public void onCreate() {
super.onCreate();
}
}
- Then, inside
AndroidManifest.xml
file, addandroid:name=".App"
<application
android:name=".App"
...
>
After that rebuild the application and the notifications will work correctly even when the app is terminated or killed.
Solution 3:[3]
On Android, for your onBackgroundMessage
to be called when the app is in the background the FCM message must be a data message without notification, look here: https://firebase.google.com/docs/cloud-messaging/android/receive
Also when the device is sleeping, to receive the messages without much delay you should disable the battery optimization, look at this: https://developer.android.com/training/monitoring-device-state/doze-standby
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Shalabyer |
Solution 2 | Ankush Chavan |
Solution 3 |