目覚まし時計などのサンプルに
Flutterでバックグラウンドでアラームを鳴らすサンプルコードです。
サンプルはボタン押したら5秒後に
バックグラウンドでアラームが鳴り
ローカル通知が出るようにしています。
目覚まし時計を作る際の参考にしていただければ幸いです。
使用しているAPIに「android_alarm_manager_plus」があるので、Andorid限定です。
サンプルの動き
『5秒後にサウンド再生』を押下
5秒後に通知が現れ、サウンドが鳴る
通知を押すと最初の画面になるので、
『サウンド停止』を押すと
音が止まります。
概要
『時間がきたら通知してアラームを鳴らす』
という単純な処理ですが
Flutterからしてみれば
- 時間がきたらローカル通知を出す
- 時間がきたら特定の処理を呼び出す
(今回はサウンドを鳴らす) - サウンドを鳴らす
という3つの処理に分かれます。
よって、それらの3つの処理を組み合わせてコーディングしてあります。
それぞれのAPI
時間がきたらローカル通知を出すAPIは
時間がきたら特定の処理を呼び出すAPIは
サウンドを鳴らすのは
がそれぞれ担当しています。
サンプルコード
百聞は一見に如かず
ということで、早速サンプルコードです。
pubspec.yaml
name: backgroundalarm
description: backgroun Alarm
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=2.18.4 <3.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
android_alarm_manager_plus: ^2.1.0
flutter_ringtone_player: ^3.2.0
flutter_local_notifications: 12.0.4
flutter_native_timezone: ^2.0.0
flutter:
uses-material-design: true
今回は
- android_alarm_manager_plus
- flutter_ringtone_player
- flutter_local_notifications
- flutter_native_timezone
の4つのAPIを利用しています。
main.dart
import 'package:flutter/material.dart';
//時間になったらバックグラウンドで音楽を鳴らすため
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
//時間になったらローカル通知を出すため
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
//ローカル通知で使うMethodChannelのため
import 'package:flutter/services.dart';
//ローカル通知で使うStreamControllerのため
import 'dart:async';
//音楽を慣らすため
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
//ローカル通知の時間をセットするためタイムゾーンの定義が必要
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
//kIsWeb(Web判定)を使うため
import 'package:flutter/foundation.dart';
//Platform判定のため
import 'dart:io';
//アラーム用のID
const int alarmID = 123456789;
//鳴らしたい秒数
const int alramSecond = 5;
//ローカル通知のための準備
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
String? selectedNotificationPayload;
final StreamController didReceiveLocalNotificationStream = StreamController.broadcast();
final StreamController selectNotificationStream = StreamController.broadcast();
const MethodChannel platform = MethodChannel('dexterx.dev/flutter_local_notifications_example');
class ReceivedNotification {
ReceivedNotification({
required this.id,
required this.title,
required this.body,
required this.payload,
});
final int id;
final String? title;
final String? body;
final String? payload;
}
const String navigationActionId = 'id_3';
@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
// ignore: avoid_print
print('notification(${notificationResponse.id}) action tapped: '
'${notificationResponse.actionId} with'
' payload: ${notificationResponse.payload}');
if (notificationResponse.input?.isNotEmpty ?? false) {
// ignore: avoid_print
print(
'notification action tapped with input: ${notificationResponse.input}');
}
}
//タイムゾーン初期化メソッド定義
Future _configureLocalTimeZone() async {
if (kIsWeb || Platform.isLinux) {
return;
}
tz.initializeTimeZones();
final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName));
}
Future main() async{
//アラームのための初期化
WidgetsFlutterBinding.ensureInitialized();
await AndroidAlarmManager.initialize();
//タイムゾーン初期化
await _configureLocalTimeZone();
//通知のための初期化
final NotificationAppLaunchDetails? notificationAppLaunchDetails = !kIsWeb &&
Platform.isLinux
? null
: await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload =
notificationAppLaunchDetails!.notificationResponse?.payload;
}
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onDidReceiveNotificationResponse:
(NotificationResponse notificationResponse) {
switch (notificationResponse.notificationResponseType) {
case NotificationResponseType.selectedNotification:
selectNotificationStream.add(notificationResponse.payload);
break;
case NotificationResponseType.selectedNotificationAction:
if (notificationResponse.actionId == navigationActionId) {
selectNotificationStream.add(notificationResponse.payload);
}
break;
}
},
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
);
//メイン画面呼び出し
runApp(const MyApp());
}
//メイン画面
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo BackGround Play Sound Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'BackGround Play Sound Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
@pragma('vm:entry-point')
static Future callSoundStart() async {
//音楽再生
FlutterRingtonePlayer.playRingtone(looping: true);
}
//アラーム・通知セット
Future setBackgroundAlarm() async {
//アラームセット
await AndroidAlarmManager.oneShot(
const Duration(seconds: alramSecond),
alarmID,
callSoundStart,
alarmClock: true,
allowWhileIdle: true,
exact: true,
wakeup: true,
);
//通知セット
await flutterLocalNotificationsPlugin.zonedSchedule(
alarmID,
'通知テストタイトル',
'通知テスト本文',
tz.TZDateTime.now(tz.local).add(const Duration(seconds: alramSecond)),
const NotificationDetails(
android: AndroidNotificationDetails(
'full screen channel id', 'full screen channel name',
channelDescription: 'full screen channel description',
priority: Priority.high,
playSound:false,
importance: Importance.high,
fullScreenIntent: true)),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime);
}
//アラーム・通知を止める
stopBackgroundArlam() async {
//アラームを止める
await AndroidAlarmManager.oneShot(
const Duration(seconds: 0), alarmID, stopSound,
exact: true, wakeup: true, alarmClock: true, allowWhileIdle: true);
//通知を止める
await flutterLocalNotificationsPlugin.cancel(alarmID);
}
@pragma('vm:entry-point')
static stopSound() async {
FlutterRingtonePlayer.stop();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("バックグラウンド再生"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed:() async {
setBackgroundAlarm();
},
child: const Text("5秒後にサウンド再生"),
),
ElevatedButton(
onPressed:() async {
stopBackgroundArlam();
},
child: const Text("サウンド停止"),
),
],
),
),
);
}
}
AndroidManifest.xml
※追加行が2か所あります。
以下ソース内の
- ここを追加!その1
- ここを追加!その2
を参照してください。
どちらも
『時間がきたらアラームを鳴らす許可をするための記載』
です。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.backgroundalarm">
<application
android:label="backgroundalarm"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
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">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</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" />
<!-- ここを追加!その1START -->
<service
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- ここを追加!その1END -->
</application>
<!-- ここを追加!その2START -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- For apps with targetSDK=31 (Android 12) -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<!-- ここを追加!その2END -->
</manifest>
サンプルコードのリンク先
上記のサンプルコードはGitHub上にも載せてあります。
補足
今回は単純な音をならすだけなので
を使用していますが、
と言う人は
を使うことになると思います。
just_audioのサンプルコードは以下記事を参照してください。
後はサンプルコードを動かしたり、ソースを修正してみたりすればなんとなく動きはつかめると思います。
では!良いflutterライフを!