Flutterで通知音を選択できるようにした話【Android編】

今回は、個人開発アプリ「かってに通知メモ」に通知音の選択機能を実装した話をまとめました。

Androidの通知音まわりはかなりクセがあって、かなり苦労したのでブログに残しておきます。同じところでハマっている方の参考になれば幸いです。


目次

きっかけ

「かってに通知メモ」は、日本語で「3分後に洗濯」「明日の朝に病院」などと入力するだけで、自動で通知をセットしてくれるアプリです。

リリース後、ユーザーから「通知音を自分で選びたい」という要望をもらいました。

確かに、通知音が選べるとより使いやすくなりますよね。ということで実装することにしました。


最初の壁:app_settingsパッケージが動かない

最初は app_settings パッケージを使って、Android の通知設定画面を開く方法を試みました。

しかしリリースビルドでは動かないという問題が発生。

MissingPluginException(No implementation found for method openSettings 
on channel com.spencerccf.app_settings/methods)

原因はProGuardによるコードの最適化でした。proguard-rules.pro にルールを追加しても解決せず、最終的にはアプリ内で通知音を選択できるUIを実装する方向に切り替えました。


wav形式を選んだ理由

通知音のファイル形式はいくつか選択肢があります。

  • .mp3
  • .wav
  • .ogg

iOSとAndroidの両方で動作することを考えて、今回は .wav 形式を選びました。

ファイル名は以下のような命名規則にしました:

short_notifybell.wav    (3秒)
short_transparent.wav   (3秒)
short_shiningtime.wav   (3秒)
medium_musicbox.wav     (6秒)
medium_chime.wav        (10秒)
medium_fantasy.wav      (11秒)
long_crystal.wav        (18秒)
long_arpeggio.wav       (28秒)

short_ medium_ long_ のプレフィックスで長さが一目瞭然になるので、管理しやすくなりました。


Androidへのファイル配置

Androidで通知発火時に指定した音を鳴らすには、res/raw/ フォルダにファイルを配置する必要があります。

android/app/src/main/res/raw/short_notifybell.wav
android/app/src/main/res/raw/short_transparent.wav
android/app/src/main/res/raw/medium_musicbox.wav
...

assets/sounds/ に置くだけでは通知発火時には使えないので注意が必要です。


AndroidNotificationDetailsの設定

通知チャンネルに音声を紐付けるには、RawResourceAndroidNotificationSound を使います。

final channelId = 'katteni_channel_$soundName';

android: AndroidNotificationDetails(
  channelId,
  'かってに通知メモ',
  channelDescription: '時間指定メモの通知',
  importance: Importance.max,
  priority: Priority.max,
  icon: '@drawable/ic_notification',
  playSound: true,
  sound: RawResourceAndroidNotificationSound(soundName),
),

ポイントはチャンネルIDを音ごとに変えること。

Androidの通知チャンネルは一度作成すると音を変更できません。そのため、音の名前をチャンネルIDに含めることで、音ごとに別チャンネルを作るようにしました。


設定画面のUI実装

設定画面では、音名と再生時間を一覧表示して選択できるようにしました。

final sounds = [
  {'value': 'short_notifybell', 'label': 'ノティファイベル (3秒)'},
  {'value': 'short_transparent', 'label': 'トランスペアレント (3秒)'},
  {'value': 'medium_musicbox', 'label': 'オルゴール (6秒)'},
  // ...
];

タップ時にはプレビュー再生して、3秒後に自動停止するようにしています。

onTap: () async {
  setState(() => _notificationSound = sound['value']!);
  await _saveSettings();
  await SoundService.playSoundByName(sound['value']!);
},

SoundService.playSoundByName() の中では、ファイルのプレフィックスで拡張子を判定しています。

static Future<void> playSoundByName(String name) async {
  try {
    final isWav = name.startsWith('short_') || 
                  name.startsWith('medium_') || 
                  name.startsWith('long_');
    final ext = isWav ? 'wav' : 'mp3';
    await _player.play(AssetSource('sounds/$name.$ext'));
    await Future.delayed(const Duration(seconds: 3));
    await _player.stop();
  } catch (e) {
    debugPrint('音声再生エラー: $e');
  }
}

ハマったポイントまとめ

1. リリースビルドでないと通知音が確認できない

デバッグビルドではデフォルト音が鳴るため、必ずリリースビルドでテストする必要があります。これに気づくまでにかなり時間を使いました。

2. 通知チャンネルは一度作ると変更できない

チャンネルIDを変えずに音だけ変えようとしても反映されません。必ずチャンネルIDを音名ごとに分けましょう。

3. アプリのデータを消去しないと新しいチャンネルが反映されない

チャンネルIDを変えた場合、古いチャンネルがキャッシュされています。テスト時は設定→アプリ→データを消去してから確認しましょう。


まとめ

Androidの通知音実装で大事なポイントをまとめます。

  • 音声ファイルは res/raw/ に配置する
  • RawResourceAndroidNotificationSound で音を指定する
  • チャンネルIDは音ごとに分ける
  • 必ずリリースビルドで動作確認する

iOSの通知音実装については、また別の記事でまとめます!


かってに通知メモは App Store・Google Play で公開中です。ぜひ使ってみてください😊

目次