How to Customize Firebase Sounds Flutter Push Notifications for iOS and Android.It can be annoying to get custom notification sounds to function correctly in Flutter.
Default notification sounds are boring. Your app deserves better. But getting custom sounds to actually play when notifications arrive? That's where things get tricky, especially when dealing with both platforms.
What You'll Require.Before we begin, please ensure that you have:
firebase_messaging plugin installed
Your custom sound files (more on formats later)
Patience (trust me on this one)
This part is crucial and often overlooked:
For iOS:
Format: .caf, .aiff, or .wav
Duration: 30 seconds or less
Quality: 22kHz sample rate recommended
For Android:
Format: .mp3, .wav, or .ogg
Keep file size reasonable (under 1MB)
I recommend having both .caf for iOS and .mp3 for Android to avoid any compatibility issues.
Add your sound file to ios/Runner/ directory
Open your iOS project in Xcode
Right-click on Runner → Add Files to "Runner"
Select your sound file and make sure "Add to target" is checked for Runner
The file structure should look like:
ios/
Runner/
notification_sound.caf
Runner/
Info.plist
AppDelegate.swift
Put your sound files in the raw resources folder for Android:
If the android/app/src/main/res/raw/ directory doesn't already exist, create it.
Copy your sound file there (let's call it notification_sound.mp3)
Your structure should be:
android/
app/
src/
main/
res/
raw/
notification_sound.mp3
The actual functioning Flutter code runs as follows:
dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationService {
static final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
static final FlutterLocalNotificationsPlugin _localNotifications =
FlutterLocalNotificationsPlugin();
static Future<void> initialize() async {
// Request permissions
await _firebaseMessaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
// Initialize local notifications
const AndroidInitializationSettings androidSettings =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings iosSettings =
DarwinInitializationSettings();
const InitializationSettings initSettings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
);
await _localNotifications.initialize(initSettings);
// Set up notification channels for Android
await _createNotificationChannel();
// Handle background messages
FirebaseMessaging.onBackgroundMessage(_backgroundMessageHandler);
// Handle foreground messages
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
}
static Future<void> _createNotificationChannel() async {
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'custom_sound_channel',
'Custom Sound Notifications',
description: 'Notifications with custom sounds',
importance: Importance.high,
sound: RawResourceAndroidNotificationSound('notification_sound'),
);
await _localNotifications
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
}
static Future<void> _backgroundMessageHandler(RemoteMessage message) async {
await _showNotificationWithSound(message);
}
static Future<void> _handleForegroundMessage(RemoteMessage message) async {
await _showNotificationWithSound(message);
}
static Future<void> _showNotificationWithSound(RemoteMessage message) async {
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
'custom_sound_channel',
'Custom Sound Notifications',
channelDescription: 'Notifications with custom sounds',
importance: Importance.high,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound('notification_sound'),
);
const DarwinNotificationDetails iosDetails = DarwinNotificationDetails(
sound: 'notification_sound.caf',
);
const NotificationDetails platformDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _localNotifications.show(
message.hashCode,
message.notification?.title ?? 'New Message',
message.notification?.body ?? 'You have a new message',
platformDetails,
);
}
}
If you're sending notifications from your server, include the sound parameter along with the crucial mutable-content and content-available flags:
json
{
"notification": {
"title": "Custom Sound Test",
"body": "This should play your custom sound",
"sound": "notification_sound.caf"
},
"data": {
"custom_sound": "notification_sound"
},
"android": {
"notification": {
"channel_id": "custom_sound_channel",
"sound": "notification_sound"
}
},
"apns": {
"payload": {
"aps": {
"sound": "notification_sound.caf",
"mutable-content": 1,
"content-available": 1,
"alert": {
"title": "Custom Sound Test",
"body": "This should play your custom sound"
}
}
}
}
}
Important:Setting the flag mutable-content=1 enables your app to process the notification prior to it being presented, while content-available=1 is meant to wake your app in the background. These two flags become quite important if you're processing custom sounds mainly on iOS.
Problem: Sound file not found or wrong format Solution:
Double-check the file is added to the Xcode project
Verify it's in .caf format
Make sure the filename in code matches exactly (case-sensitive)
Problem: Most likely a channel configuration issue Solution:
Clear app data and reinstall
Notification channels are immutable once created
Make sure the raw resource name doesn't have file extensions
Both platforms need proper permissions. Add this to your initialization:
dart
NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
provisional: false,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('User granted permission');
} else {
print('Permission denied');
}
Foreground testing: Send a test notification while app is open
Background testing: Put app in background and send notification
Kill state testing: Force close app and send notification
Use Firebase Console or your server to send test notifications. Make sure to test on real devices - emulators can be unreliable for sound testing.
Convert your audio files using online tools or FFmpeg for proper formats
Keep sound files short (2-3 seconds) for better user experience
Test on multiple devices - sound behavior can vary
Consider having a fallback to default sound if custom sound fails
When things don't work (and they will):
✅ Sound files in correct directories?
✅ Proper file formats for each platform?
✅ Notification channels configured correctly?
✅ Permissions granted?
✅ Testing on real devices?
✅ App reinstalled after adding sound files?
Taking some time to set custom notification sounds for users' engagement is all worth it. Basically, just make sure your file is set up to run on both platforms and that your notification channels are properly configured.
Remember that notification channels on Android cannot be edited after they have been created. Should you require changes, just either increment the channel ID or clear the app data to be able to test.
I hope this will help you to avoid some issues I encountered. Feel free to ask any questions regarding any situations you come across, or leave a comment!