I have implemented the notificationServiceExtension as mentioned by onesignal in it's documentation but now I am not being able to run background notification handler in one signal.
I have implemented the following code in main activity:::
package com.example.just_normal
import com.onesignal.OneSignal
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
private val methodchannel = "background/notification";
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
OneSignal.OSRemoteNotificationReceivedHandler { context, osNotificationReceivedEvent ->
val CHANNEL : MethodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger , methodchannel)
CHANNEL.invokeMethod("onBackgroundNotification" , osNotificationReceivedEvent);
// osNotificationReceivedEvent.complete(null);
}
}
}
In the above code I am trying to call the flutter method immediately after the notification has been received in the background. How can I run flutter method immediately after notification is received in backgound. Please help....
use this in the init function where you are registering onesignal in the app:
OneSignal.shared.setNotificationWillShowInForegroundHandler((OSNotificationReceivedEvent event) {
event.complete(event.notification);
});
Now you will be able to get notifications even when the app got killed.
Related
handle background notification in onesingnal flutter. i want to print my notification in console when app is in background. for foreground notification i used setNotificationWillShowInForegroundHandler. now for handling background notification flutter.
when the app is terminated you can’t print anything but if the app is running in the background you can print notifications or notification additional data like this.
add NotificationServiceExtension.jave file in android > app > src > main > kotlin > next to mainActivity
add the following code:
package your bundle_id;
import android.content.Context;
import android.util.Log;
import org.json.JSONObject;
import com.onesignal.OSNotification;
import com.onesignal.OSMutableNotification;
import com.onesignal.OSNotificationReceivedEvent;
import com.onesignal.OneSignal.OSRemoteNotificationReceivedHandler;
#SuppressWarnings("unused")
public class NotificationServiceExtension implements
OSRemoteNotificationReceivedHandler {
#Override
public void remoteNotificationReceived(Context context, OSNotificationReceivedEvent notificationReceivedEvent) {
OSNotification notification = notificationReceivedEvent.getNotification();
Log.i("OneSignalExample", "Notification Data: " + notification);
// Example of modifying the notification's accent color
OSMutableNotification mutableNotification = notification.mutableCopy();
mutableNotification.setExtender(builder -> {
// Sets the accent color to Green on Android 5+ devices.
//Force remove push from Notification Center after 30 seconds
builder.setTimeoutAfter(30000);
return builder;
});
JSONObject data = notification.getAdditionalData();
//log.i will print data in body of notification
Log.i("OneSignalExample", "Received Notification Data: " + data);
// If complete isn't call within a time period of 25 seconds,
OneSignal internal logic will show the original notification
// To omit displaying a notification, pass `null` to complete()
notificationReceivedEvent.complete(mutableNotification);
}
}
don't forget to add these tags just below application tag in manifest android
<meta-data android:name="com.onesignal.NotificationServiceExtension"
android:value="your_bundle_id.NotificationServiceExtension" />
TL;DR how can I have an Android sensor permanently running/active/registered for my app, even if I close it?
Objective:
I'm making a Flutter application that counts your steps using the pedometer package,
which uses the built-in sensor TYPE_STEP_COUNTER of Android,
which returns the # of steps taken since last boot (iOS). On Android, any steps taken before installing the app are not counted.
How I implemented it:
When the app is actively running in the foreground, each step causes
a myStepCount to increment by 1.
In all other cases (phone locked, went to home-screen, closed the app...), the android TYPE_STEP_COUNTER sensor should
still be running in the background, and once I open my app again, the
difference between new stepCount and last saved stepCount (saved
using shared_prefs) will be calculated and added to myStepCount.
Important:
The TYPE_STEP_COUNTER sensor must be permanently running/stay registered in the background, even after I lock my phone, go to the home-screen, or close the app...
Observations:
On my Samsung Galaxy A02s, my app works perfectly fine, as it it supposed to
(as described above). That is because on that phone I also have the
Google Fit app installed, which tracks your steps 24/7 (so the
TYPE_STEP_COUNTER sensor is permanently registered).
On my Samsung Galaxy S7, my app does not work as it's supposed to.
myStepCount gets incremented when I take steps while the app is
running in the foreground. But steps taken while the app is closed
will NOT be added to myStepCount once I open the app again.
Note: I don't have any other step-counting-apps like Google Fit on this phone.
Conclusion:
I need to find a way to register the TYPE_STEP_COUNTER sensor from my Flutter app, and keep it registered even after I close the app.
2 Attempted (but unsuccessful) Solutions:
1st Attempt:
Calling Native Android Code from my Flutter Code to register the sensor
This is my main.dart file (with the unimportant parts left out for simplicity):
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
if (Platform.isAndroid) {
_activateStepCounterSensor();
} else if (Platform.isIOS) {
//TODO check if anything is needed to to here
}
}
void _activateStepCounterSensor() async {
MethodChannel _stepCounterChannel = MethodChannel('com.cedricds.wanderapp/stepCounter'); //convention
dynamic result = await _stepCounterChannel.invokeMethod('activateStepCounterSensor');
switch (result) {
case "success":
//The following line gets printed when I run the flutter app on my Samsung Galaxy S7:
print('_activateStepCounterSensor(): successfully registered step counter sensor for android');
break;
case "error":
print('_activateStepCounterSensor(): failed to register step counter sensor (not available) for android');
//TODO display errorpage (because app is completely useless in this case)
break;
default:
print('_activateStepCounterSensor(): unknown result: $result');
break;
}
}
//build() and other lifecycle-methods and helper methods: not important for this question
}
This is my MainActivity.kt file:
package com.cedricds.wanderapp
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import android.widget.Toast
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity2: FlutterActivity(), SensorEventListener {
private val STEP_COUNTER_CHANNEL = "com.cedricds.wanderapp/stepCounter";
private lateinit var channel: MethodChannel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, STEP_COUNTER_CHANNEL)
channel.setMethodCallHandler { call, result ->
when(call.method){ //this is like switch-case statement in Java
"activateStepCounterSensor" -> {
activateStepCounterSensor(result)
}
}
}
}
private var sensorManager : SensorManager?=null
private var sensor: Sensor ?= null
private fun activateStepCounterSensor(result: MethodChannel.Result) {
//This line gets printed when I run the flutter app, so the method gets called successfully:
Log.d("Android", "Native Android: activateStepCounterSensor()")
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager?.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
if (sensor == null) {
Toast.makeText(this, "missing hardware.", Toast.LENGTH_LONG).show()
result.error("error", "error", "error")
} else {
sensorManager?.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)
//This line gets printed:
Log.d("Android", "Native Android: registered TYPE_STEP_COUNTER")
//and never unregister that listener
result.success("success")
}
}
override fun onSensorChanged(p0: SensorEvent?) {}
override fun onAccuracyChanged(p0: Sensor?, p1: Int) {}
}
Despite the few print(...) and Log.d(...) being printed in the console as expected, the app doesn't work how I expected it to work. When I exit the app, walk for example 50 steps, then open the app again, those 50 steps are missing. It seems the sensor is being unregistered somewhere.
2nd Attempt:
Modifying the pedometer package's code by removing unregisterListener(...):
The only changes I did to the file were 2 Log.d(...) statements and more importantly, commenting out a specific line of code.
modified SensorStreamHandler.kt from the pedometer package:
package com.example.pedometer
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Looper
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import android.os.Handler
import android.util.Log
class SensorStreamHandler() : EventChannel.StreamHandler {
private var sensorEventListener: SensorEventListener? = null
private var sensorManager: SensorManager? = null
private var sensor: Sensor? = null
private lateinit var context: Context
private lateinit var sensorName: String
private lateinit var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
constructor(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, sensorType: Int) : this() {
this.context = flutterPluginBinding.applicationContext
this.sensorName = if (sensorType == Sensor.TYPE_STEP_COUNTER) "StepCount" else "StepDetection"
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager!!.getDefaultSensor(sensorType)
this.flutterPluginBinding = flutterPluginBinding
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
Log.d("Pedometer", "Native Android: onListen()")
if (sensor == null) {
events!!.error("1", "$sensorName not available",
"$sensorName is not available on this device");
} else {
sensorEventListener = sensorEventListener(events!!);
sensorManager!!.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
override fun onCancel(arguments: Any?) {
Log.d("Pedometer", "Native Android: onCancel()")
//The only change I did: commenting out the following line:
// sensorManager!!.unregisterListener(sensorEventListener);
}
}
This also did not solve my problem. So if someone knows how I can permanently register the TYPE_STEP_COUNTER sensor in my flutter app, please let me know.
Update:
I've contacted one of the developers of the pedometer package, and he suggested me to use flutter_foreground_service (which is developed by the same team/company as pedometer). It works.
But I would still find it interesting, if there is another way (maybe similar to my 2 failed attempts).
I have a flutter application, adding AppCheck and using Android Emulator to test and debug.
I am testing the access of Realtime database. From my Firebase Console, AppCheck shows that all my access are of this type: Unverified: invalid requests. I have followed this: https://firebase.google.com/docs/app-check/android/debug-provider.
my app/build.gradle
dependencies {
...
//implementation 'com.google.firebase:firebase-appcheck-safetynet:16.0.0-beta02'
implementation 'com.google.firebase:firebase-appcheck-debug:16.0.0-beta03'
...
}
In my main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Initialize AppCheck
await FirebaseAppCheck.instance.activate();
...
In MainActivity.kt, I have the following:
import io.flutter.embedding.android.FlutterActivity
import android.os.Bundle
import android.util.Log
import com.google.firebase.FirebaseApp
import com.google.firebase.appcheck.FirebaseAppCheck
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory
//import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory
class MainActivity: FlutterActivity() {
// For Debug Only. Do not do this for Production
override fun onCreate(savedInstanceState: Bundle?) {
FirebaseApp.initializeApp(this)
Log.e("MainActivity", "onCreate")
val firebaseAppCheck = FirebaseAppCheck.getInstance()
firebaseAppCheck.installAppCheckProviderFactory(DebugAppCheckProviderFactory.getInstance())
super.onCreate(savedInstanceState)
}
}
From logcat, I can see the following log
com.google.firebase.appcheck.debug.internal.DebugAppCheckProvider: Enter this debug secret into the allow list in the Firebase Console for your project: xxxxxxxxxxxxx
Based on the token, I use managed debug token and set it to a debug token.
Using the AppCheck
Realtime Database only shows unverified requests
I am expecting to see verified requests showing up.
I also use Android Studio profiler to monitor the Network, I can see a request
POST https://firebaseappcheck.googleapis.com/v1beta/projects/<app>/apps/<appid>:exchangeSafetyNetToken?key=<key>
In the payload is a JSON safetynet token.
I get a response of 403.
Note that I have not turn on enforcement on the realtime database.
What am I missing with AppCheck? Am I supposed to see verified request using the emulator or only on real physical device (release mode)?
I tried with onCreate but couldn't get it to work.
Using a MethodChannel worked instead 🎉:
// main.dart
void main() async {
// ...
await Firebase.initializeApp();
await FirebaseAppCheck.instance.activate();
// FirebaseAppCheck when enforced would block incoming requests from Android emulator and iOS simulator too.
// This kDebugMode check gets a debug token from FirebaseAppCheck which can then be added on the Firebase
// console so that the emulator and simulator can be allowed to access to Firestore.
if (kDebugMode) {
try {
const MethodChannel methodChannel = MethodChannel("method-channel");
await methodChannel.invokeMethod("getFirebaseAppCheckDebugToken");
} catch (e) {
print("FirebaseAppCheck debug token error: $e");
}
}
// ...
}
// MainActivity.kt
package com.yourname.applicationname
import android.util.Log
import androidx.annotation.NonNull
import com.google.firebase.FirebaseApp
import com.google.firebase.appcheck.FirebaseAppCheck
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "method-channel").setMethodCallHandler { call, result ->
if (call.method == "getFirebaseAppCheckDebugToken") {
FirebaseApp.initializeApp(this)
Log.d("configureFlutterEngine", "FirebaseApp.initializeApp")
val firebaseAppCheck = FirebaseAppCheck.getInstance()
Log.d("configureFlutterEngine", "firebaseAppCheck")
firebaseAppCheck.installAppCheckProviderFactory(DebugAppCheckProviderFactory.getInstance())
Log.d("configureFlutterEngine", "installAppCheckProviderFactory")
result.success("Yay!")
}
}
}
}
Result on every app launch on the Android emulator in Flutter debug mode:
I managed to workaround this issue (in debug mode) by adding the following condition:
if (kReleaseMode) {
await FirebaseAppCheck.instance.activate();
}
To ensure that AppCheck works in your flutter project make sure you have the firebase_app_check package installed. Once you do you can use it as such:
import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:flutter/foundation.dart';
main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// AppCheck
await FirebaseAppCheck.instance.activate(
androidProvider: kDebugMode ? AndroidProvider.debug : AndroidProvider.playIntegrity
);
// The rest of your code
}
Using the ternary operator above allows you to set it and forget it. Just remember to enable Enable App Check enforcement to get things running.
Things to remember:
Dependencies. Make sure you have implementation platform('com.google.firebase:firebase-bom:31.2.0') in your dependencies in app/build.gradle file (31.2.0 as of writing). When you have bom installed you don't need to import each firebase service individually. The bom takes care of that for you.
You can compare bom versions here.
AppCheck will only work in release builds. For dev use, there's the debug code. Good thing is you only need to set the debug code for every new install per device. That usually means you can keep creating new builds for as long as you don't uninstall it from your emulator. But it never hurts to remember the first few characters just in case.
This may be an isolated case but if appcheck still won't work in prod try installing from Playstore by creating a new release.
Importing package:flutter/foundation.dart gives you access to kDebugMode
My goal is to select a picture directly from phone's Gallery and use it in my app, like you would share it with other apps like WhatsApp, Mail, Messenger..etc etc.
I need to:
Select the picture from gallery.
Select my app from the share dialog.
Launch a specific screen passing in the picture.
I've seen a lot of sharing packages but I'm not looking to load a picture by accessing the gallery from my app, but the other way around.
This package https://pub.dev/packages/receive_sharing_intent seems to do what I need but not in a very transparent and native looking way.. as instead of app icon and name, in the share dialog it shows the iOS extension created for this plugin..
In this post How do I share an image on iOS and Android using Flutter? I see Simon's answer could be the solution, but trying to implement it I'm getting quite a few errors after pasting the Kotlin sample code into MainActivity.kt file. I guess it has to be updated.
This is my MainActivity.kt file with added code from Simon's answer:
package com.vinny.fixit_cloud_biking
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView,"channel:me.albie.share/share").setMethodCallHandler { methodCall, _ ->
if (methodCall.method == "shareFile") {
shareFile(methodCall.arguments as String)
}
}
}
private fun shareFile(path:String) {
val imageFile = File(this.applicationContext.cacheDir,path)
val contentUri = FileProvider.getUriForFile(this,"me.albie.share",imageFile)
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.type="image/jpg"
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
startActivity(Intent.createChooser(shareIntent,"Compartir usando"))
}
}
Errors onCreate():
Cannot access 'androidx.lifecycle.lifecycleOwner' which is a super type of 'com.vinny.fixit_cloud_biking.MainActivity'. Check your module class path for missing or conflicting dependencies.
onCreate: overrides nothing
Errors MethodChannel:
Cannot access 'androidx.lifecycle.lifecycleOwner' which is a super type of 'io.flutter.embedding.android.FLutterActivity'. Check your module class path for missing or conflicting dependencies.
Unresolved reference: flutterView
As always many thanks for your help.
I am trying to customize notification sound of firebase_messaging in flutter. On foreground I am implementing flutter_local_notifications package to deliver notification where I have setup custom sound and vibration. But in case of background, notification is handled by default notification channel. Is there any way I can create a notification channel or use the one I just created using flutter_local_notifications package?
For those of you arriving here because your FCM messages aren't acting as you prefer while your app is in the background:
You will probably need to create a Notification Channel if you want a "heads-up" notification when the app is in the background and you want to have your own custom sound accompanying it. The default Notification Channel used by the FCM does not have the "pop on screen" setting enabled and uses the default system sound. You can see this by going to the app's settings on your device.
OP is using the flutter_local_notifications package, which is pretty much the "go-to" package for notification handling in flutter. You can create your own Notification Channels via the createNotificationChannel method and assign your desired parameters (including sound and priority level). This is the quick and easier way of getting your notifications to act as you want them.
If you want to create your own Notification Channel without the flutter_local_notifications package, then you will have to modify your MainActivity.kt (or Java) file in its native form. It's not overly complicated, but it is more low-level than just using the flutter_local_notifications package. This Medium post describes how to do that (for Android).
In Flutter you can create android notification channel yourself thru MainActivity.kt or MainActivity.java file depending whichever you have in your project Android folder. Good guide here - using MainActivity.kt which is easy and working, tried myself - it works:
package com.example.new_channel //Your own package name
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.net.Uri;
import android.media.AudioAttributes;
import android.content.ContentResolver;
class MainActivity: FlutterActivity() {
private val CHANNEL = "somethinguniqueforyou.com/channel_test" //The channel name you set in your main.dart file
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
if (call.method == "createNotificationChannel"){
val argData = call.arguments as java.util.HashMap<String, String>
val completed = createNotificationChannel(argData)
if (completed == true){
result.success(completed)
}
else{
result.error("Error Code", "Error Message", null)
}
} else {
result.notImplemented()
}
}
}
private fun createNotificationChannel(mapData: HashMap<String,String>): Boolean {
val completed: Boolean
if (VERSION.SDK_INT >= VERSION_CODES.O) {
// Create the NotificationChannel
val id = mapData["id"]
val name = mapData["name"]
val descriptionText = mapData["description"]
val sound = "your_sweet_sound"
val importance = NotificationManager.IMPORTANCE_HIGH
val mChannel = NotificationChannel(id, name, importance)
mChannel.description = descriptionText
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"+ getApplicationContext().getPackageName() + "/raw/your_sweet_sound");
val att = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
mChannel.setSound(soundUri, att)
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(mChannel)
completed = true
}
else{
completed = false
}
return completed
}
}
And this is with MainActivity java:
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;
import android.media.AudioAttributes;
import androidx.core.app.NotificationCompat;
import android.net.Uri;
import android.content.ContentResolver;
this is a code
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(“new_email_arrived_channel”, “My Emailer”, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setShowBadge(true);
notificationChannel.setDescription(“”);
AudioAttributes att = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
notificationChannel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + “://” + getPackageName() + “/raw/bell”), att);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(new long[]{400, 400});
notificationChannel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(notificationChannel);
}
In Flutter side you may need trigger for initiating the process and naming the notification channel. It is from the same source above:
String _statusText = "Waiting...";
final String _finished = "Finished creating channel";
final String _error = "Error while creating channel";
static const MethodChannel _channel =
MethodChannel('somethinguniqueforyou.com/channel_test');
Map<String, String> channelMap = {
"id": "CHAT_MESSAGES",
"name": "Chats",
"description": "Chat notifications",
};
void _createNewChannel() async {
try {
await _channel.invokeMethod('createNotificationChannel', channelMap);
setState(() {
_statusText = _finished;
});
} on PlatformException catch (e) {
_statusText = _error;
print(e);
}
}
Now for all Android versions you need only this format notification payload:
"notification": {
"body": "Test notification",
"title": "Test Test Test",
"click_action": "FLUTTER_NOTIFICATION_CLICK",
"sound": "your_custom_sound"
"android_channel_id": "channel_id_youcreated",
},
'to':
"",
},
Sound file name is not necessary in the above given notification payload if you assigned that sound to your notification channel thru MainActivity.kt or java file. However, it is necessary for older Android versions as they will use the sound file directly.
If you check in the Firebase Console, when sending the notification, you can especify a channel ID in "other options", there you can write the channel you have already created using flutter_local_notifications.
Hope this helps!
Since you are already using flutter_local_notifications there is an alternate way to the implementation mentioned by #Elmar for Android.
As per the FCM Legacy API Doc
The notification's channel id (new in Android O).
The app must create a channel with this channel ID before any
notification with this channel ID is received.
If you don't send this channel ID in the request, or if the channel ID
provided has not yet been created by the app, FCM uses the channel ID
specified in the app manifest.
Step 1: Define android notification channel
/// The plugin
FlutterLocalNotificationsPlugin? flutterLocalNotificationsPlugin;
/// File name should not have the extension
static const String soundFileName = 'file_name_of_sound';
/// Custom notification channel
final channel = const AndroidNotificationChannel(
'custom_notification_channel_01',
'Notification channel with custom sound notifications',
description: 'This channel is used for notifications with a custom sound.',
importance: Importance.high,
playSound: true,
sound: RawResourceAndroidNotificationSound(soundFileName),
);
Step 2: Create notification channel, this should be done early in the code, preferrably where the FirebaseMessaging is initialized
await flutterLocalNotificationsPlugin
?.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
Step 3: Include the android channel id in push notification from the backend.
Now you are good to go, test this one locally using postman or a client of your choice.
METHOD: POST
URL: https://fcm.googleapis.com/fcm/send
HEADER: Don't forget to add Authorization=key=${server_key_from_firebase_console}
BODY:
{
"to": "fcm_token",
"notification": {
"android_channel_id": "custom_notification_channel_01",
"title": "Title of the custom notification",
"body": "An important notification with a custom sound",
"sound": "custom_sound_file_name"
}
}
PS: The sound is optional, if you have multiple custom sounds, enable the play sound in the channel and include the file name of the custom sound in the notification payload.