How to Set Alarms in Flutter Using the Alarm Package

alarm demo image

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
bg-mode

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.

info-plist

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()
app-delegate

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()
Dart

Then, 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,
);
Dart

And finally set the alarm:

await Alarm.set(alarmSettings: alarmSettings)
Dart

Note 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)
Dart

This is how to stop/cancel your alarm:

await Alarm.stop(id)
Dart

This 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)),
   ),
 );
Dart

This 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());
Dart

This 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),
      ),
    );
  }
Dart

Full 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),
      ),
    );
  }
}
Dart

alarmnotificationscreen.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")),
            ],
          )
        ],
      ),
    );
  }
}
Dart

Thank you for reading this article.

1 thought on “How to Set Alarms in Flutter Using the Alarm Package”

Comments are closed.