Firebase push notification with custom sound (Flutter) - dart

I'm currently using firebase messaging cloud to push notification for my app. I'm trying to make a custom notification sound for the push notification. I believe that it can be done by putting "sound: blabla.mp3" inside the payload, but how do i define the sound inside dart page?

Use flutter_local_notifications package
AndroidNotificationDetails androidNotificationsDetails = AndroidNotificationDetails(
'your other channel id',
'your other channel name',
'your other channel description',
importance: Importance.Max,
priority: Priority.Max,
enableLights: true,
playSound: true,
sound: RawResourceAndroidNotificationSound('notification'),
);
Note: you should add notification.mp3 file in android/app/src/main/res/raw/notification.mp3 and don't forget to specify playSound:
playSound: true,
this worked for me in the foreground / background and when the app is closed.

It can be done flutter_local_notifications plugin.
First, you should define channels for both Android and iOS:
const androidPlatformChannel = AndroidNotificationDetails(
'ANDROID_CHANNEL_ID',
'Name',
'Description',
color: Color.fromARGB(255, 0, 0, 0),
importance: Importance.max,
sound: RawResourceAndroidNotificationSound('notification_sound'),
playSound: true,
priority: Priority.high,
showWhen: false,
);
const iOSPlatformChannel = IOSNotificationDetails(
sound: 'notification_sound.aiff',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const platformChannel = NotificationDetails(
android: androidPlatformChannel,
iOS: iOSPlatformChannel,
);
Then show a notification:
await flutterLocalNotificationsPlugin.show(
id,
title,
body,
platformChannel,
payload: notification.payload,
);
IMPORTANT! notification_sound.aiff file should be copied with XCode

you can do this simply by calling the sound and playing it in the firebase configure method.
widget._firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print('on message $message');
AudioCache player = new AudioCache();
const alarmAudioPath = "sounds/notification.mp3";
player.play(alarmAudioPath);
},
onResume: (Map<String, dynamic> message) async {
print('on resume $message');
},
onLaunch: (Map<String, dynamic> message) async {
print('on launch $message');
},
);
this wouldnt be effective as the file wouldnt be played if the app is in the background

To create a custom notification sound for both Android you will need to predefine a Notification Channel for your app. I used Flutter Local Notifications plugin to do this. In there, you can specify the parameters for your Android Notification Channel such as
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'your other channel id',
'your other channel name',
'your other channel description',
sound: RawResourceAndroidNotificationSound('slow_spring_board');
Note: slow_spring_board.mp3 is stored in the android\app\src\main\res\raw section of your app. Do not include the file extension when calling RawResourceAndroidNotificationSound()
There are other steps required to get it setup for iOS devices (mp3 are not supported). Once this is all done, you can begin working on your cloud messaging portion, in there you'll have to reference the notification channel id, and the sound file. I recommend including the audio file extension in that code.

You must create notification channel first with following code
Application.kt
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService
import io.flutter.plugins.pathprovider.PathProviderPlugin;
import io.flutter.plugin.common.MethodChannel
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.net.Uri;
import android.media.AudioAttributes;
import android.content.ContentResolver;
class Application : FlutterApplication(), PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
this.createChannel()
FlutterFirebaseMessagingService.setPluginRegistrant(this)
}
override fun registerWith(registry: PluginRegistry?) {
// createChannel();
FirebaseCloudMessagingPluginRegistrant.registerWith(registry);
}
private fun createChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel
val name: String = getString(R.string.default_notification_channel_id)
val channel = NotificationChannel(name, "default", NotificationManager.IMPORTANCE_HIGH)
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"+ getApplicationContext().getPackageName() + "/raw/sample");
val att = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
channel.setSound(soundUri, att)
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
/*
val id = mapData["id"]
val name = mapData["name"]
val descriptionText = mapData["description"]
val sound = "sample"
val importance = NotificationManager.IMPORTANCE_HIGH
val mChannel = NotificationChannel(id, name, importance)
mChannel.description = descriptionText
// 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
*/
}
}
}
And android manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.foodlz.orders">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />
<!-- 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. -->
<application
android:name=".Application"
android:label="test App"
android:icon="#mipmap/ic_launcher">
<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">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in #style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<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" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/default_notification_channel_id"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="#drawable/ic_appstore" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/colorPrimary" />
</application>
</manifest>
finally you must put your notification sound.wav file in res/raw
that's it

Related

How to Use file in /Library/Sounds as notification sound in ios flutter

I just got a request: Can you download sound file from remote server and make it a notification sound?
Here is my code (for ios)
//package i used
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:dio/dio.dart';
import 'package:path/path.dart' as pathService;
import 'package:path_provider/path_provider.dart';
//Download file we needed
final iosLibraryDir = await getApplicationLibrary();
final soundFilename = 'notification1.wav';
final downloadPath = pathService.join(iosLibraryDir.path, 'Sounds', soundFilename);
Dio dio = Dio();
await dio.download(remoteURL, downloadPath);
//set up notification
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin.initialize(
InitializationSettings(
iOS: IOSInitializationSettings(requestSoundPermission: true)
);
await flutterLocalNotificationsPlugin.show(
1,
'Test Title',
'Test Body',
NotificationDetails(
iOS: IOSNotificationDetails(
presentSound: true,
sound: soundFilename
)
)
);
Notification shown but no sound played
I also try path like '/Library/Sounds/notification1.wav' and full path report from getApplicationLibrary(), but none of them worked.
Is this caused by ios cannot found the path or Apple's security policy not allowed it?

Deeplinking with a domain name

I have the following code in my App.js:
import React, { useState, useRef, useEffect } from 'react';
import { SafeAreaView, Text } from 'react-native';
import { NavigationContainer, useLinking } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const Screen1 = () => <SafeAreaView><Text>Screen1</Text></SafeAreaView>;
const Screen2 = () => <SafeAreaView><Text>Screen2</Text></SafeAreaView>;
export default function App() {
const ref = useRef();
const [isReady, setIsReady] = useState(false);
const [initialState, setInitialState] = useState();
const { getInitialState } = useLinking(ref, {
prefixes: ['http://example.com', 'mychat://'],
config: {
screens: {
Screen2: 'screen-2',
},
},
});
useEffect(() => {
getInitialState().then((state) => {
if (state !== undefined) setInitialState(state);
setIsReady(true);
});
}, [getInitialState]);
if (!isReady) return null;
return (
<NavigationContainer ref={ref} initialState={initialState}>
<Stack.Navigator>
<Stack.Screen name='Screen1' component={Screen1} />
<Stack.Screen name='Screen2' component={Screen2} />
</Stack.Navigator>
</NavigationContainer>
);
}
Most of them are copied from https://reactnavigation.org/docs/deep-linking/ and https://reactnavigation.org/docs/use-linking/.
In the docs there is prefixes: ['https://mychat.com', 'mychat://'], I just changed https://mychat.com to http://example.com. But it doesn't seem to work.
When I open the following links in Safari:
mychat:// (works, gets redirected to app Screen1)
mychat://screen-2 (works, gets redirected to app Screen2)
http://example.com (just opens the link in the browser, no popup to redirect to app)
What change do I need to make to redirect the domain name to the mobile app? Am I missing something?
You need to use a domain that you have access to alongside a server.
Your server needs to host a couple of files, typically within the .well-known directory:
apple-app-site-association (note the .json is not needed)
assetlinks.json
You also need to enable some entitlements within your app for iOS, this may also be true for Android. On iOS, this will be enabling the Associated Domains entitlement alongside an entry for webcredentials:yourdomain.com
The documentation is pretty good to go through to give an understanding on what needs to be done in order to achieve Universal Links
https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html
https://developer.android.com/training/app-links/verify-site-associations
Examples:
iOS - https://stackoverflow.com/.well-known/apple-app-site-association
Android - https://stackoverflow.com/.well-known/assetlinks.json

No camera found on Chrome IOS with CapacitorJS and Ionic

I have created a PWA with ionic and capacitor JS following this guide: https://ionicframework.com/docs/react/your-first-app.
After i have added it to firebase and start testet the code, i have runned into a issue on Chrome on IOS mobiles. It works on android and also web browsers.
But when i click the take photo button it says "No camera found" and the browser dont ask to let me use the camera. If i try the same thing on Safari, then it asks for the camera.
Here is the url to see the test: https://phototest-46598.web.app/tab1
Does anybody experience the same problem? My guess is that it is a new problem since the guide seems to work without problems.
Here is my code - i have followed the linked tutorial but not added native support because i only want to use it as a PWA.
hooks/usePhotoGallery.js file
import { useState, useEffect } from "react";
import { useCamera } from '#ionic/react-hooks/camera';
import { CameraResultType, CameraSource, CameraPhoto, Capacitor,
FilesystemDirectory } from "#capacitor/core";
export function usePhotoGallery() {
const { getPhoto } = useCamera();
const [photos, setPhotos] = useState<Photo[]>([]);
const takePhoto = async () => {
const cameraPhoto = await getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100
});
const fileName = new Date().getTime() + '.jpeg';
const newPhotos = [{
filepath: fileName,
webviewPath: cameraPhoto.webPath
}, ...photos];
setPhotos(newPhotos)
};
return {
photos,
takePhoto
};
}
export interface Photo {
filepath: string;
webviewPath?: string;
base64?: string;
}
Tab2.tsx file
import React from 'react';
import { camera, trash, close } from 'ionicons/icons';
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar,
IonFab, IonFabButton, IonIcon, IonGrid, IonRow,
IonCol, IonImg, IonActionSheet } from '#ionic/react';
import ExploreContainer from '../components/ExploreContainer';
import { usePhotoGallery } from '../hooks/usePhotoGallery';
import './Tab2.css';
const Tab2: React.FC = () => {
const { photos, takePhoto } = usePhotoGallery();
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Photo Gallery</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonGrid>
<IonRow>
{photos.map((photo, index) => (
<IonCol size="6" key={index}>
<IonImg src={photo.webviewPath} />
</IonCol>
))}
</IonRow>
</IonGrid>
<IonFab vertical="bottom" horizontal="center" slot="fixed">
<IonFabButton onClick={() => takePhoto()}>
<IonIcon icon={camera}></IonIcon>
</IonFabButton>
</IonFab>
</IonContent>
</IonPage>
);
};
export default Tab2;
The camera plugin when running on web uses navigator.mediaDevices.getUserMedia, which is not supported on Chrome for iOS (prior to iOS 14.3).
I've run your app and it says "No camera found" and under it there is a "Choose image" button, if you click it you'll be prompted to take a picture or choose from the photo library, that's the expected behavior, when there is no Camera or no support for navigator.mediaDevices.getUserMedia it fallbacks to using a input with type file.

How to get Push-Notification on iOS working with Firebase and Flutter?

I tried many different tutorials, followed e.g. this one https://medium.com/flutterpub/enabling-firebase-cloud-messaging-push-notifications-with-flutter-39b08f2ed723
But I'm not able to receive any Push-Notifications on my iPhone. It works perfectly on Android.
I use firebase_messaging: ^4.0.0+4 and flutter_local_notifications: ^0.6.1
The problem is that none of the listeners (onMessage, onResume or onLaunch) is getting called in iOS although a token is received with getToken()
There is no error message. I can't find the reason for this behavior. I would be glad if someone could help me out.
Thanks.
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("IM HERE ON MESSAGE");
print('on message $message');
String message_as_String = json.encode(message);
print(message_as_String);
String title = "test";
String body = "test";
String screen = "test";
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
'your channel id', 'your channel name', 'your channel description',
playSound: false, importance: Importance.Max, priority: Priority.High);
var iOSPlatformChannelSpecifics =
new IOSNotificationDetails(presentSound: false);
var platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
body,
title,
platformChannelSpecifics,
payload: screen,
);
},
onResume: (Map<String, dynamic> message) async {
print('on resume $message');
String screen = message["screen"];
},
onLaunch: (Map<String, dynamic> message) async {
print('on launch $message');
},
);
if (Platform.isIOS) iOS_Permission();
_firebaseMessaging.getToken().then((token){
print(token);
});
}
The documentation for flutter_local_notifications states the following:
NOTE: this plugin registers itself as the delegate to handle incoming notifications and actions. This may cause problems if you're using other plugins for push notifications (e.g. firebase_messaging) as they will most likely do the same and it's only possible to register a single delegate.
A few questions:
Why do you want to utilise both packages?
Have you enabled Remote notifications in Background Modes under your Target in Xcode?
Have you created a certificate for notifications on iOS? (APNS: Apple Push Notification Service (link)
Let me know how this turns out for you :)
I know this is quite late, but I had the same issue.
My problem was that I didn't download the 'GoogleService-Info.plist' again after I added the Key to Firebase.
After downloading it again and adding it to the Xcode project, push notifications worked like a charm :)
Hope this helps

BroadcastReceiver Not Firing after boot

Unable to get my Xamarin.Android app to fire Toast after boot. I checked many accepted solutions but none seem to resolve my problem. I've also tried various "working" examples but haven't had any luck so clearly I'm missing something.
Device: Samsung Galaxy S3
API: 19
Android Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.novak" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
<uses-sdk android:minSdkVersion="16" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application android:label="ReceiverApp">
<receiver android:enabled="true"
android:exported="true"
android-permission="android.permission.RECEIVE_BOOT_COMPLETED"
android:name="com.novak.BootReceiver" >
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>
</manifest>
BootReceiver.cs
using Android.App;
using Android.Widget;
using Android.Content;
namespace ReceiverApp
{
[BroadcastReceiver]
[IntentFilter(new[] { Intent.ActionBootCompleted })]
public class BootReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
Toast.MakeText(context, "Receiver", ToastLength.Long).Show();
}
}
}
It would appear that what I want to do is not possible. Once my app is installed the app sits in a stopped state and must be executed manually the first time before it can receive broadcasts
[How to start a Service when .apk is Installed for the first time
P.S: On real device it takes a while to fire the event, ie: 2 minutes after unlock the screen on my Samsung. On emulator it takes very short.
I wrote the code below to notice when it will fire:
Autorun.cs - Just add this file into your project, that's it, it will start after the boot. No manifest modification needed.
using Android;
using Android.App;
using Android.Content;
// we need to ask permission to be notified of these events
[assembly: UsesPermission (Manifest.Permission.ReceiveBootCompleted)]
namespace XamarinCookbook
{
// we want this to fire when the device boots
[BroadcastReceiver]
[IntentFilter (new []{ Intent.ActionBootCompleted })]
public class ServiceStarter : BroadcastReceiver
{
public override void OnReceive (Context context, Intent intent)
{
#region Start chrome
var mesaj = "Autorun started";
Android.Widget.Toast.MakeText(Android.App.Application.Context, mesaj, Android.Widget.ToastLength.Long).Show();
var uri = Android.Net.Uri.Parse("https://500px.com");
var intent1 = new Intent(Intent.ActionView, uri);
intent1.AddFlags(ActivityFlags.NewTask);
intent1.SetPackage("com.android.chrome");
try
{
context.StartActivity(intent1);
}
catch (ActivityNotFoundException ex)
{
//Chrome browser not installed
intent.SetPackage(null);
context.StartActivity(intent1);
}
#endregion
/*
#region Real code
// just start the service
var myIntent = new Intent (context, typeof(XamarinService));
context.StartService (myIntent);
#endregion
*/
}
}
}
VS Solution:
https://drive.google.com/open?id=1iYZQ2YCvBkyym9-2FvU7KXoBKUWsMdlT

Resources