Setup
IOS
Step 1
First, open your project in Xcode, select your Runner and then click the Signing & Capabilities tab. In the Background Modes section, make sure to enable:
- Audio, AirPlay, and Picture in Picture
- Background fetch
This allows the app to check alarms in the background.
Step 2
Then, open your Info.plist and add the key Permitted background task scheduler identifiers
, with the item com.gdelataillade.fetch
inside.
It should add this in your Info.plist code:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.gdelataillade.fetch</string>
</array>
This authorizes the app to run background tasks using the specified identifier.
Step 3
Open your AppDelegate and add the following imports:
import UserNotifications
import alarm
Finally, add the following to your application(_:didFinishLaunchingWithOptions:)
method:
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
SwiftAlarmPlugin.registerBackgroundTasks()
This configures the app to manage foreground notifications and setup background tasks.
Don’t forget to run pod install --repo-update
to update your pods.
Android
Step 1
In your android/app/build.gradle
, make sure you have the following config:
android {
compileSdkVersion 34
[...]
defaultConfig {
[...]
multiDexEnabled true
}
}
Step 2
Then, add the following permissions to your AndroidManifest.xml
within the <manifest></manifest>
tags:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
Step 3
Finally, if you want your notifications to show in full screen even when the device is locked (androidFullScreenIntent
parameter), add these attributes in <activity>
:
<activity
android:showWhenLocked="true"
android:turnScreenOn="true">
Step 4
Inside the tag of your AndroidManifest.xml
, add the following declarations (if you need notification-on-kill feature):
<application>
[...]
<service android:name="com.gdelataillade.alarm.services.NotificationOnKillService" />
[...]
</application>
This setup is essential for showing a notification when the app is terminated. You can enable this notification with Alarm.setNotificationOnAppKillContent
.
Step 5
Future<void> checkAndroidScheduleExactAlarmPermission() async {
final status = await Permission.scheduleExactAlarm.status;
print('Schedule exact alarm permission: $status.');
if (status.isDenied) {
print('Requesting schedule exact alarm permission...');
final res = await Permission.scheduleExactAlarm.request();
print('Schedule exact alarm permission ${res.isGranted ? '' : 'not'} granted.');
}
}
How to use
Add to your pubspec.yaml:
flutter pub add alarm
First, you have to initialize the Alarm service in your main
function:
await Alarm.init()
DartThen, you have to define your alarm settings:
final alarmSettings = AlarmSettings(
id: 42,
dateTime: dateTime,
assetAudioPath: 'assets/alarm.mp3',
loopAudio: true,
vibrate: true,
volume: 0.8,
fadeDuration: 3.0,
notificationTitle: 'This is the title',
notificationBody: 'This is the body',
enableNotificationOnKill: Platform.isIOS,
);
DartAnd finally set the alarm:
await Alarm.set(alarmSettings: alarmSettings)
DartNote that if notificationTitle
and notificationBody
are both empty, iOS will not show the notification and Android will show an empty notification.
If you enabled enableNotificationOnKill
, you can choose your own notification title and body by using this method before setting your alarms:
await Alarm.setNotificationOnAppKillContent(title, body)
DartThis is how to stop/cancel your alarm:
await Alarm.stop(id)
DartThis is how to snooze alarm:
final now = DateTime.now();
Alarm.set(
alarmSettings: widget.alarmSettings.copyWith(
dateTime: DateTime(
now.year,
now.month,
now.day,
now.hour,
now.minute,
).add(const Duration(minutes: 1)),
),
);
DartThis is how to run some code when the alarm starts ringing. I implemented it as a stream, so even if your app was previously killed, your custom callback can still be triggered.
Alarm.ringStream.stream.listen((_) => yourOnRingCallback());
DartThis is how listen alarm and navigates to screen:
static StreamSubscription<AlarmSettings>? subscription;
subscription ??= Alarm.ringStream.stream.listen(navigateToRingScreen);
Future<void> navigateToRingScreen(AlarmSettings alarmSettings) async {
await Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) =>
AlarmNotificationScreen(alarmSettings: alarmSettings),
),
);
}
DartFull Code:
main.dart
import 'dart:async';
import 'dart:io';
import 'package:alarm/alarm.dart';
import 'package:alarm/model/alarm_settings.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_alarm_simple_demo/AlarmNotification.dart';
import 'package:permission_handler/permission_handler.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Alarm.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Alarm Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late List<AlarmSettings> alarms;
static StreamSubscription<AlarmSettings>? subscription;
@override
void initState() {
super.initState();
//notifcation permission
checkAndroidNotificationPermission();
//schedule alarm permission
checkAndroidScheduleExactAlarmPermission();
loadAlarms();
subscription ??= Alarm.ringStream.stream.listen(navigateToRingScreen);
//listen alarm if active than navigate to alarm screen
}
void loadAlarms() {
setState(() {
alarms = Alarm.getAlarms();
alarms.sort((a, b) => a.dateTime.isBefore(b.dateTime) ? 0 : 1);
});
}
Future<void> checkAndroidNotificationPermission() async {
final status = await Permission.notification.status;
if (status.isDenied) {
alarmPrint('Requesting notification permission...');
final res = await Permission.notification.request();
alarmPrint(
'Notification permission ${res.isGranted ? '' : 'not '}granted',
);
}
}
Future<void> navigateToRingScreen(AlarmSettings alarmSettings) async {
await Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) =>
AlarmNotificationScreen(alarmSettings: alarmSettings),
),
);
loadAlarms();
}
Future<void> checkAndroidScheduleExactAlarmPermission() async {
final status = await Permission.scheduleExactAlarm.status;
if (kDebugMode) {
print('Schedule exact alarm permission: $status.');
}
if (status.isDenied) {
if (kDebugMode) {
print('Requesting schedule exact alarm permission...');
}
final res = await Permission.scheduleExactAlarm.request();
if (kDebugMode) {
print(
'Schedule exact alarm permission ${res.isGranted ? '' : 'not'} granted.');
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: ListView(
children: List.generate(
alarms.length,
(index) => ListTile(
title: Text(alarms[index].dateTime.toString()),
)),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
var alarmDateTime = DateTime.now().add(const Duration(seconds: 30));
final alarmSettings = AlarmSettings(
id: 42,
dateTime: alarmDateTime,
assetAudioPath: 'assets/blank.mp3',
loopAudio: true,
vibrate: true,
volume: 0.8,
fadeDuration: 3.0,
notificationTitle: 'This is the title',
notificationBody: 'This is the body',
enableNotificationOnKill: Platform.isIOS,
);
await Alarm.set(alarmSettings: alarmSettings);
loadAlarms();
},
child: const Icon(Icons.add),
),
);
}
}
Dartalarmnotificationscreen.dart
import 'package:alarm/alarm.dart';
import 'package:alarm/model/alarm_settings.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AlarmNotificationScreen extends StatefulWidget {
AlarmSettings alarmSettings;
AlarmNotificationScreen({super.key, required this.alarmSettings});
@override
State<AlarmNotificationScreen> createState() =>
_AlarmNotificationScreenState();
}
class _AlarmNotificationScreenState extends State<AlarmNotificationScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Alram is ringing......."),
Text(widget.alarmSettings.notificationTitle),
Text(widget.alarmSettings.notificationBody),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
//skip alarm for next time
final now = DateTime.now();
Alarm.set(
alarmSettings: widget.alarmSettings.copyWith(
dateTime: DateTime(
now.year,
now.month,
now.day,
now.hour,
now.minute,
).add(const Duration(minutes: 1)),
),
).then((_) => Navigator.pop(context));
},
child: const Text("Snooze")),
ElevatedButton(
onPressed: () {
//stop alarm
Alarm.stop(widget.alarmSettings.id)
.then((_) => Navigator.pop(context));
},
child: const Text("Stop")),
],
)
],
),
);
}
}
DartThank you for reading this article.
thank you for this blog