How to Add Custom Sounds to Firebase Push Notifications in Flutter (iOS & Android)

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.

The Problem

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 Need

What You'll Require.Before we begin, please ensure that you have:

Sound File Requirements

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.

Setting Up the Sound Files

iOS Setup

  1. Add your sound file to ios/Runner/ directory

  2. Open your iOS project in Xcode

  3. Right-click on Runner → Add Files to "Runner"

  4. 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

Android Setup

Put your sound files in the raw resources folder for Android:

  1. If the android/app/src/main/res/raw/ directory doesn't already exist, create it.

  2. 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

Code Implementation

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,

    );

  }

}

Server-Side Configuration

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. 

Common Issues and Solutions

iOS Sound Not Playing

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)

Android Sound Not Working

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

Permissions Issues

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');

}

Testing Your Implementation

  1. Foreground testing: Send a test notification while app is open

  2. Background testing: Put app in background and send notification

  3. 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.

Pro Tips

  • 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

Debugging Checklist

When things don't work (and they will):

  1. ✅ Sound files in correct directories?

  2. ✅ Proper file formats for each platform?

  3. ✅ Notification channels configured correctly?

  4. ✅ Permissions granted?

  5. ✅ Testing on real devices?

  6. ✅ App reinstalled after adding sound files?

Conclusion

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!