Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@Andrious
Last active March 1, 2022 05:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Andrious/4e9c6d0a4f8dfb8cac783df41114f33d to your computer and use it in GitHub Desktop.
Save Andrious/4e9c6d0a4f8dfb8cac783df41114f33d to your computer and use it in GitHub Desktop.
A Dart library file that works with the Flutter plugin, android_alarm_manager.
///
/// Copyright (C) 2020 Andrious Solutions
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Created 11 Mar 2020
///
///
/// AlarmManager
///
/// Works with the Flutter plugin, android_alarm_manager,
/// that accesses the Android AlarmManager service running Dart code
/// in the background when alarms fire.
///
/// If `alarmClock` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setAlarmClock`.
///
/// If `allowWhileIdle` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setExactAndAllowWhileIdle` or
/// `AlarmManagerCompat.setAndAllowWhileIdle`.
///
/// If `exact` is passed as `true`, the timer will be created with Android's
/// `AlarmManagerCompat.setExact`. When `exact` is `false` (the default), the
/// timer will be created with `AlarmManager.set`.
///
/// If `wakeup` is passed as `true`, the device will be woken up when the
/// alarm fires. If `wakeup` is false (the default), the device will not be
/// woken up to service the alarm.
///
/// If `rescheduleOnReboot` is passed as `true`, the alarm will be persisted
/// across reboots. If `rescheduleOnReboot` is false (the default), the alarm
/// will not be rescheduled after a reboot and will not be executed.
///
import 'dart:async' show Future;
import 'dart:isolate' show ReceivePort, SendPort;
import 'dart:ui' show IsolateNameServer;
import 'package:flutter/material.dart' show WidgetsFlutterBinding;
import 'package:android_alarm_manager/android_alarm_manager.dart';
class AlarmManager {
static Future<bool> init({
bool alarmClock = false,
bool allowWhileIdle = false,
bool exact = false,
bool wakeup = false,
bool rescheduleOnReboot = false,
DateTime startAt,
}) async {
// Don't continue if already called.
if (_init) return _init;
_init = true;
bool init;
try {
// Ensure WidgetsBinding is available when the Alarm Service is initialized.
WidgetsFlutterBinding.ensureInitialized();
// Initialize the plugin
init = await AndroidAlarmManager.initialize();
}catch(ex){
getError(Exception(ex));
return false;
}
if (!init) getError(Exception("AndroidAlarmManager not initialize!"));
// Initialize the callback operation.
_Callback.init();
if (alarmClock != null) _alarmClock = alarmClock;
if (allowWhileIdle != null) _allowWhileIdle = allowWhileIdle;
if (exact != null) _exact = exact;
if (wakeup != null) _wakeup = wakeup;
if (rescheduleOnReboot != null) _rescheduleOnReboot = rescheduleOnReboot;
if (startAt != null) _startAt = startAt;
return true;
}
static bool _init = false;
static bool _alarmClock = false;
static bool _allowWhileIdle = false;
static bool _exact = false;
static bool _wakeup = false;
static bool _rescheduleOnReboot = false;
static DateTime _startAt;
static Object _error;
static bool get hasError => _error != null;
static bool get inError => _error != null;
/// Cancels a timer.
///
/// If a timer has been scheduled with `id`, then this function will cancel
/// it.
///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future<bool> cancel(int id) async {
bool cancel;
try {
cancel = await AndroidAlarmManager.cancel(id);
} catch (ex) {
cancel = false;
getError(ex);
}
return cancel;
}
/// Records the `error` when it occurs.
static Exception getError([Object error]) {
Exception ex = _error;
if (error == null) {
_error = null;
} else {
if (error is! Exception) error = Exception(error.toString());
_error = error;
}
if (ex == null) ex = error;
return ex;
}
/// Schedules a one-shot timer to run `callback` after time `delay`.
///
/// The `callback` will run whether or not the main application is running or
/// in the foreground. It will run in the Isolate owned by the
/// AndroidAlarmManager service.
///
/// `callback` must be either a top-level function or a static method from a
/// class.
///
/// `callback` can be `Function()` or `Function(int)`
///
/// The timer is uniquely identified by `id`. Calling this function again
/// with the same `id` will cancel and replace the existing timer.
///
/// `id` will passed to `callback` if it is of type `Function(int)`
///
/// If `alarmClock` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setAlarmClock`.
///
/// If `allowWhileIdle` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setExactAndAllowWhileIdle` or
/// `AlarmManagerCompat.setAndAllowWhileIdle`.
///
/// If `exact` is passed as `true`, the timer will be created with Android's
/// `AlarmManagerCompat.setExact`. When `exact` is `false` (the default), the
/// timer will be created with `AlarmManager.set`.
///
/// If `wakeup` is passed as `true`, the device will be woken up when the
/// alarm fires. If `wakeup` is false (the default), the device will not be
/// woken up to service the alarm.
///
/// If `rescheduleOnReboot` is passed as `true`, the alarm will be persisted
/// across reboots. If `rescheduleOnReboot` is false (the default), the alarm
/// will not be rescheduled after a reboot and will not be executed.
///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future<bool> oneShot(
Duration delay,
int id,
Function(int id) callback, {
bool alarmClock,
bool allowWhileIdle,
bool exact,
bool wakeup,
bool rescheduleOnReboot,
}) async {
assert(_init,"oneShot(): `AlarmManager.init()` must be first called.");
if (!_init) {
getError("oneShot(): `AlarmManager.init()` must be first called.");
return _init;
}
//
bool oneShot = false;
if (delay == null || delay.inMicroseconds == 0) {
getError("oneShot(): `delay` is null or zero.");
return oneShot;
}
if (id == null || id <= 0) {
getError("oneShot(): `id` is null or less than or zero.");
return oneShot;
}
// Collect the Callback routine to eventually call.
if (callback != null) _Callback.oneShots[id] = callback;
try {
oneShot = await AndroidAlarmManager.oneShot(
delay,
id,
_Callback.oneShot,
alarmClock: alarmClock ?? _alarmClock,
allowWhileIdle: allowWhileIdle ?? _allowWhileIdle,
exact: exact ?? _exact,
wakeup: wakeup ?? _wakeup,
rescheduleOnReboot: rescheduleOnReboot ?? _rescheduleOnReboot,
);
} catch (ex) {
oneShot = false;
getError(ex);
}
return oneShot;
}
/// Schedules a one-shot timer to run `callback` at `time`.
///
/// The `callback` will run whether or not the main application is running or
/// in the foreground. It will run in the Isolate owned by the
/// AndroidAlarmManager service.
///
/// `callback` must be either a top-level function or a static method from a
/// class.
///
/// `callback` can be `Function()` or `Function(int)`
///
/// The timer is uniquely identified by `id`. Calling this function again
/// with the same `id` will cancel and replace the existing timer.
///
/// `id` will passed to `callback` if it is of type `Function(int)`
///
/// If `alarmClock` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setAlarmClock`.
///
/// If `allowWhileIdle` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setExactAndAllowWhileIdle` or
/// `AlarmManagerCompat.setAndAllowWhileIdle`.
///
/// If `exact` is passed as `true`, the timer will be created with Android's
/// `AlarmManagerCompat.setExact`. When `exact` is `false` (the default), the
/// timer will be created with `AlarmManager.set`.
///
/// If `wakeup` is passed as `true`, the device will be woken up when the
/// alarm fires. If `wakeup` is false (the default), the device will not be
/// woken up to service the alarm.
///
/// If `rescheduleOnReboot` is passed as `true`, the alarm will be persisted
/// across reboots. If `rescheduleOnReboot` is false (the default), the alarm
/// will not be rescheduled after a reboot and will not be executed.
///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future<bool> oneShotAt(
DateTime datetime,
int id,
Function(int id) callback, {
bool alarmClock,
bool allowWhileIdle,
bool exact,
bool wakeup,
bool rescheduleOnReboot,
}) async {
assert(_init,"oneShotAt(): `AlarmManager.init()` must be first called.");
if (!_init) {
getError("oneShotAt(): `AlarmManager.init()` must be first called.");
return _init;
}
bool oneShotAt = false;
if (datetime == null) return oneShotAt;
DateTime time = datetime.toLocal();
if (time == null || time.hour < 0) {
getError("oneShotAt(): `datetime` is less than or zero.");
return oneShotAt;
}
assert(id != null && id > 0);
if (id == null || id <= 0) {
getError("oneShotAt(): `id` is null or less than or zero.");
return oneShotAt;
}
// Collect the Callback routine to eventually call.
if (callback != null) _Callback.oneShotAts[id] = callback;
try {
oneShotAt = await AndroidAlarmManager.oneShotAt(
time,
id,
_Callback.oneShotAt,
alarmClock: alarmClock ?? _alarmClock,
allowWhileIdle: allowWhileIdle ?? _allowWhileIdle,
exact: exact ?? _exact,
wakeup: wakeup ?? _wakeup,
rescheduleOnReboot: rescheduleOnReboot ?? _rescheduleOnReboot,
);
} catch (ex) {
oneShotAt = false;
getError(ex);
}
return oneShotAt;
}
/// Schedules a repeating timer to run `callback` with period `duration`.
///
/// The `callback` will run whether or not the main application is running or
/// in the foreground. It will run in the Isolate owned by the
/// AndroidAlarmManager service.
///
/// `callback` must be either a top-level function or a static method from a
/// class.
///
/// `callback` can be `Function()` or `Function(int)`
///
/// The repeating timer is uniquely identified by `id`. Calling this function
/// again with the same `id` will cancel and replace the existing timer.
///
/// `id` will passed to `callback` if it is of type `Function(int)`
///
/// If `startAt` is passed, the timer will first go off at that time and
/// subsequently run with period `duration`.
///
/// If `exact` is passed as `true`, the timer will be created with Android's
/// `AlarmManager.setRepeating`. When `exact` is `false` (the default), the
/// timer will be created with `AlarmManager.setInexactRepeating`.
///
/// If `wakeup` is passed as `true`, the device will be woken up when the
/// alarm fires. If `wakeup` is false (the default), the device will not be
/// woken up to service the alarm.
///
/// If `rescheduleOnReboot` is passed as `true`, the alarm will be persisted
/// across reboots. If `rescheduleOnReboot` is false (the default), the alarm
/// will not be rescheduled after a reboot and will not be executed.
///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future<bool> periodic(
Duration duration,
int id,
Function(int id) callback, {
DateTime startAt,
bool alarmClock, // Essentially ignored.
bool allowWhileIdle, // Essentially ignored.
bool exact,
bool wakeup,
bool rescheduleOnReboot,
}) async {
assert(_init,"periodic(): `AlarmManager.init()` must be first called.");
if (!_init) {
getError("periodic(): `AlarmManager.init()` must be first called.");
return _init;
}
bool periodic = false;
if (duration == null || duration.inSeconds <= 0) {
getError("periodic(): `duration` is less than or zero.");
return periodic;
}
if (id == null || id <= 0) {
getError("periodic(): `id` is less than or zero.");
return periodic;
}
// Collect the Callback routine to eventually call.
if (callback != null) _Callback.periodics[id] = callback;
try {
periodic = await AndroidAlarmManager.periodic(
duration,
id,
_Callback.periodic,
startAt: startAt ?? _startAt,
exact: exact ?? _exact,
wakeup: wakeup ?? _wakeup,
rescheduleOnReboot: rescheduleOnReboot ?? _rescheduleOnReboot,
);
} catch (ex) {
periodic = false;
getError(ex);
}
return periodic;
}
}
/// This class is involved in the callback operations.
class _Callback {
//
/// A port used to communicate from a background isolate to the UI isolate.
static final ReceivePort _port = ReceivePort();
/// Collect the callback functions identified by id.
static final Map<int, Function(int id)> oneShots = Map();
static final Map<int, Function(int id)> oneShotAts = Map();
static final Map<int, Function(int id)> periodics = Map();
static void init() {
// Register the UI isolate's SendPort to communicate with the background isolate.
bool reg = IsolateNameServer.registerPortWithName(
_port.sendPort,
_oneShot,
);
reg = IsolateNameServer.registerPortWithName(
_port.sendPort,
_oneShotAt,
);
reg = IsolateNameServer.registerPortWithName(
_port.sendPort,
_periodic,
);
// Register for events from the background isolate.
_port.listen((map) {
try {
int id = map.keys.first;
String type = map.values.first;
Function(int id) func;
switch (type) {
case _oneShot:
func = oneShots.remove(id);
break;
case _oneShotAt:
func = oneShotAts.remove(id);
break;
case _periodic:
func = periodics[id];
}
func(id);
} catch (ex) {
AlarmManager.getError(ex);
}
});
}
// The callback for our alarm
static Future<void> oneShot(int id) async {
print(oneShots); // Although it's defined in the foreground it's null here.
// This will be null if we're running in the background.
SendPort uiSendPort = IsolateNameServer.lookupPortByName(_oneShot);
// Identify which callback function to call
uiSendPort?.send({id: _oneShot});
}
// The callback for our alarm
static Future<void> oneShotAt(int id) async {
// This will be null if we're running in the background.
SendPort uiSendPort = IsolateNameServer.lookupPortByName(_oneShotAt);
// Identify which callback function to call
uiSendPort?.send({id: _oneShotAt});
}
// The callback for our alarm
static Future<void> periodic(int id) async {
// This will be null if we're running in the background.
SendPort uiSendPort = IsolateNameServer.lookupPortByName(_periodic);
// Identify which callback function to call
uiSendPort?.send({id: _periodic});
}
}
const String _oneShot = 'oneShot';
const String _oneShotAt = 'oneShotAt';
const String _periodic = 'periodic';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment