← Back to posts Cover image for Custom Notification Sounds in Flutter: A Cross-Platform Guide for iOS and Android

Custom Notification Sounds in Flutter: A Cross-Platform Guide for iOS and Android

· 6 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

Custom notification sounds – they’re a small detail, but they can make a huge difference in your app’s user experience. Whether it’s a unique chime for an important reminder, a specific alert for a prayer app, or just giving users the freedom to personalize their notifications, custom audio adds a polished touch.

However, getting custom notification sounds to work consistently across iOS and Android isn’t always straightforward. Each platform has its own requirements for sound file placement and configuration. Let’s dive into how to tackle this challenge in Flutter, ensuring your app delivers the desired audio experience every time.

The Challenge: Platform Differences

The core problem lies in how iOS and Android handle notification sounds.

  • Android uses Notification Channels, where you define the sound for each channel. The sound files live in the app’s res/raw directory.
  • iOS uses UNNotificationSound and expects sound files to be part of your main app bundle. It also has specific format and duration requirements.

Our goal is to abstract these differences using a Flutter plugin.

Our Go-To Solution: flutter_local_notifications

For local notifications, flutter_local_notifications is a go-to solution. It provides a unified API to handle notifications, including custom sounds, across both platforms.

First, add it to your pubspec.yaml:

dependencies:
  flutter_local_notifications: ^17.0.0 # Use the latest version

Then, run flutter pub get.

Platform-Specific Setup for Sound Files

Before we write any Dart code, we need to place our custom sound files correctly.

Android Setup

  1. Create res/raw: Inside your Flutter project, navigate to android/app/src/main/res/. If a raw directory doesn’t exist, create it: android/app/src/main/res/raw/.
  2. Add Sound Files: Place your .mp3, .ogg, or .wav sound files directly into this raw directory.
    • Example: android/app/src/main/res/raw/custom_chime.mp3
    • Important: File names should be lowercase and contain only a-z, 0-9, and underscores (e.g., my_alert_sound.mp3).

iOS Setup

  1. Add to Runner: Open your iOS project in Xcode (ios/Runner.xcworkspace).
  2. Drag and Drop: Drag your .aiff, .wav, or .caf sound files directly into the Runner folder in Xcode’s project navigator.
  3. Target Membership: When prompted, ensure “Copy items if needed” is checked, and make sure your app’s target is selected under “Add to targets”.
    • Example: custom_chime.aiff
    • Important: iOS notification sounds have a maximum duration of 30 seconds. Sounds longer than this will play the default system sound.

Implementing Custom Sounds in Flutter

Now, let’s wire it up in your Flutter app.

1. Initialize the Plugin

You’ll need to initialize flutter_local_notifications early in your app, typically in main.dart.

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> initializeNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('@mipmap/ic_launcher');

  const DarwinInitializationSettings initializationSettingsDarwin =
      DarwinInitializationSettings(
        requestAlertPermission: true,
        requestBadgePermission: true,
        requestSoundPermission: true,
      );

  const InitializationSettings initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: initializationSettingsDarwin,
  );

  await flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse response) async {
      // Handle notification tap
      print('Notification tapped: ${response.payload}');
    },
  );
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeNotifications();
  runApp(const MyApp());
}

2. Define Notification Details with Custom Sound

When creating notification details, specify the custom sound. For Android, you’ll reference the file name without its extension. For iOS, you’ll use the full filename including the extension.

Future<void> showCustomSoundNotification() async {
  // IMPORTANT: For Android, use RawResourceAndroidNotificationSound
  // and provide the resource name without the extension.
  // The resource name must be lowercase and contain only a-z, 0-9, and underscores.
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'custom_sound_channel_id',
    'Custom Sound Channel',
    channelDescription: 'Channel with a custom notification sound',
    importance: Importance.max,
    priority: Priority.high,
    playSound: true,
    sound: RawResourceAndroidNotificationSound('custom_chime'), // Android: 'custom_chime' refers to custom_chime.mp3 in res/raw
  );

  // IMPORTANT: For iOS, use DarwinNotificationDetails
  // and provide the full filename including the extension.
  // The file must be in the main app bundle.
  const DarwinNotificationDetails iOSPlatformChannelSpecifics =
      DarwinNotificationDetails(
    presentAlert: true,
    presentBadge: true,
    presentSound: true,
    sound: 'custom_chime.aiff', // iOS: 'custom_chime.aiff' refers to the file in your Runner bundle
  );

  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
    iOS: iOSPlatformChannelSpecifics,
  );

  await flutterLocalNotificationsPlugin.show(
    0, // Notification ID
    'Time for your custom alert!',
    'This notification uses a special sound.',
    platformChannelSpecifics,
    payload: 'custom_payload',
  );
}

3. Handling User-Selectable Sounds

If users can choose their sound, you would store the selected sound’s filename (e.g., “custom_chime” for Android, “custom_chime.aiff” for iOS) in SharedPreferences or similar. Then, retrieve this filename and pass it to RawResourceAndroidNotificationSound or DarwinNotificationDetails respectively.

// Example of a function that gets the user's chosen sound
String getUserSelectedSoundFileName(TargetPlatform platform) {
  // In a real app, you'd fetch this from SharedPreferences or a database
  if (platform == TargetPlatform.android) {
    return 'user_selected_tune'; // Corresponds to user_selected_tune.mp3 in res/raw
  } else {
    return 'user_selected_tune.aiff'; // Corresponds to user_selected_tune.aiff in the main bundle
  }
}

Future<void> showUserSelectedSoundNotification() async {
  final String androidSound = getUserSelectedSoundFileName(TargetPlatform.android);
  final String iOSSound = getUserSelectedSoundFileName(TargetPlatform.iOS);

  final AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'user_sound_channel_id',
    'User Sound Channel',
    channelDescription: 'Channel with user-selected sound',
    importance: Importance.max,
    priority: Priority.high,
    playSound: true,
    sound: RawResourceAndroidNotificationSound(androidSound),
  );

  final DarwinNotificationDetails iOSPlatformChannelSpecifics =
      DarwinNotificationDetails(
    presentAlert: true,
    presentBadge: true,
    presentSound: true,
    sound: iOSSound,
  );

  final NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
    iOS: iOSPlatformChannelSpecifics,
  );

  await flutterLocalNotificationsPlugin.show(
    1,
    'Your personalized alert!',
    'You chose this sound!',
    platformChannelSpecifics,
  );
}

Common Troubleshooting Tips

  • File Naming and Format:
    • Android: Lowercase, a-z, 0-9, _. .mp3, .ogg, .wav. No extension in Dart code.
    • iOS: Full filename with extension. .aiff, .wav, .caf. Max 30 seconds.
  • File Placement: Double-check android/app/src/main/res/raw/ and that the iOS file is part of the main bundle in Xcode.
  • Android Channel Issues: If you change the sound for an existing channel, users might need to reinstall the app or clear app data for the change to take effect, as Android channels are created once. Consider creating a new channel ID for major sound changes.
  • Silent Mode (iOS): On iOS, notification sounds (custom or default) will not play if the device is in silent/vibrate mode. This is standard iOS behavior for most apps, as notification sounds generally respect the user’s silent mode setting.
  • Permissions: Ensure your app has notification permissions granted. flutter_local_notifications handles requesting these, but it’s always good to verify.
  • Restart App: After changing sound files or channel configurations, a full app restart (not just hot reload) is often necessary for changes to take effect.

Conclusion

Implementing custom notification sounds in Flutter is a powerful way to enhance your app’s engagement and personalization. While it requires careful attention to platform-specific details, flutter_local_notifications provides an excellent abstraction layer. By following these steps and keeping common pitfalls in mind, you’ll be able to deliver a rich, audio-driven notification experience to your users on both iOS and Android. Happy chiming!

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease

Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease

Flutter is gaining traction as a robust framework for Linux desktop development, offering a smoother experience compared to traditional toolkits like GTK and Qt. This post will explore why Flutter excels for Linux apps, highlight its advantages, and provide practical insights for developers looking to leverage Flutter for their desktop projects.

Cover image for Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows

Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows

Deploying Flutter desktop apps on Windows can be challenging, especially regarding code signing and preventing Windows from blocking installations. This post will guide developers through the process of signing their Flutter desktop applications, exploring alternatives to the Microsoft Store like creating executable installers, and covering the necessary steps to ensure a smooth user experience.

Cover image for Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows

Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows

Developers frequently voice frustrations about Flutter SDK version management and installation processes, particularly on Windows. This post will explore best practices for managing multiple Flutter versions using tools like FVM, discuss integrating Flutter with package managers like Winget for a smoother Windows setup, and offer strategies to ensure a consistent development environment across projects and teams.