
Facilitates seamless alarm and notification scheduling across Android and iOS, ensuring easy integration and cross-platform functionality with straightforward installation steps.
Alarmee is a Kotlin/Compose Multiplatform library designed to simplify scheduling alarms and notifications on both Android and iOS platforms. With Alarmee, you can schedule one-time or repeating alarms, display platform-specific notifications, and now supports push notifications using Firebase Cloud Messaging (Android) and Apple Push Notification service (iOS).
[!WARNING] Upgrading from v1.x?
Check out the Migration Guide to update your code for version 2.0.
Be sure to show your support by starring βοΈ this repository, and feel free to contribute if you're interested!
Alarmee powers notifications in real-world apps:
settings.gradle.kts file, add Maven Central to your repositories:repositories {
mavenCentral()
}alarmee dependency.alarmee-push dependency.Open libs.versions.toml:
[versions]
alarmee = "2.4.0" // Check latest version
[libraries]
alarmee = { group = "io.github.tweener", name = "alarmee", version.ref = "alarmee" } // For local notifications only
alarmee = { group = "io.github.tweener", name = "alarmee-push", version.ref = "alarmee" } // For both local & push notifications
Then in your module build.gradle.kts add:
dependencies {
// Only one of these is needed, depending on your use case
implementation(libs.alarmee)
implementation(libs.alarmee.push)
}In your module build.gradle.kts add:
dependencies {
val alarmee_version = "2.4.0" // Check latest version
// Only one of these is needed, depending on your use case
implementation("io.github.tweener:alarmee:$alarmee_version") // For local notifications only
implementation("io.github.tweener:alarmee-push:$alarmee_version") // For both local & push notifications
}To get started with Alarmee, you need to provide a platform-specific configuration for Android and iOS. Follow these steps.
In your commonMain source set, declare the following function to provide platform-specific configuration:
expect fun createAlarmeePlatformConfiguration(): AlarmeePlatformConfigurationIn the androidMain source set, implement the actual function and return an AlarmeeAndroidPlatformConfiguration:
actual fun createAlarmeePlatformConfiguration(): AlarmeePlatformConfiguration =
AlarmeeAndroidPlatformConfiguration(
notificationIconResId = R.drawable.ic_notification,
notificationIconColor = androidx.compose.ui.graphics.Color.Red, // Defaults to Color.Transparent is not specified
useExactScheduling = true, // Enable exact alarm scheduling for more precise timing (Android 12+, requires SCHEDULE_EXACT_ALARM permission)
notificationChannels = listOf(
AlarmeeNotificationChannel(
id = "dailyNewsChannelId",
name = "Daily news notifications",
importance = NotificationManager.IMPORTANCE_HIGH,
soundFilename = "notifications_sound",
),
AlarmeeNotificationChannel(
id = "breakingNewsChannelId",
name = "Breaking news notifications",
importance = NotificationManager.IMPORTANCE_LOW,
),
// List all the notification channels you need here
)
)In the iosMain source set, implement the actual function and return an AlarmeeIosPlatformConfiguration:
val platformConfiguration: AlarmeePlatformConfiguration = AlarmeeIosPlatformConfigurationThere are multiple ways to initialize Alarmee depending on your setup.
With Compose:
val alarmService: AlarmeeService = rememberAlarmeeService(
platformConfiguration = createAlarmeePlatformConfiguration()
)Without Compose:
val alarmeeService = createAlarmeeService()
alarmeeService.initialize(platformConfiguration = createAlarmeePlatformConfiguration())Alarmee also supports push notifications on mobile via Firebase (Android) or APNs (iOS). If you're already using Firebase in your app, you can pass your own Firebase instance to avoid initializing it twice.
With Compose:
val alarmService: MobileAlarmeeService = rememberAlarmeeMobileService(
platformConfiguration = createAlarmeePlatformConfiguration()
)val alarmService: MobileAlarmeeService = rememberAlarmeeMobileService(
platformConfiguration = createAlarmeePlatformConfiguration(),
firebase = Firebase
)Without Compose:
val alarmeeService = createAlarmeeMobileService()
alarmeeService.initialize(platformConfiguration = createAlarmeePlatformConfiguration())val alarmeeService = createAlarmeeMobileService()
alarmeeService.initialize(
platformConfiguration = createAlarmeePlatformConfiguration(),
firebase = Firebase
)You can then use this instance to schedule or cancel alarms from your shared code.
[!IMPORTANT] Before using Alarmee, make sure the Notifications permission is granted on the target platform (Android official documentation, iOS official documentation).
Alternativally, you can use
moko-permissionsto easily handle permissions for you.
After initializing AlarmeeService, you can access the notification services:
To send local notifications, use the local service:
val localService = alarmService.local
localService.schedule(...) // For instanceThis is available on all targets (Android, iOS, desktop, web, etc.).
To access push notifications (e.g. Firebase):
val pushService = alarmService.pushThis is only available on Android and iOS. On non-mobile targets, pushService will be null.
You can schedule an alarm to be triggered at a specific time of the day, using AlarmeeService#schedule(...). When the alarm is triggered, a notification will be displayed.
For instance, to schedule an alarm on January 12th, 2025, at 5 PM:
localService.schedule(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've scheduled an Alarmee!",
notificationBody = "This is the notification that will be displayed at the specified date and time.",
scheduledDateTime = LocalDateTime(year = 2025, month = Month.JANUARY, dayOfMonth = 12, hour = 17, minute = 0),
deepLinkUri = "https://www.example.com", // A deep link URI to be retrieved in MainActivity#onNewIntent() on Android and in AppDelegate#userNotificationCenter() on iOS
imageUrl = "https://rickandmortyapi.com/api/character/avatar/1.jpeg", // Optional parameter to display an image within the notification
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.HIGH,
channelId = "dailyNewsChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)You can specify a RepeatInterval parameter, which allows scheduling an alarm to repeat hourly, daily, weekly, monthly, yearly or custom, based on the specified scheduledDateTime.
You can use a fixed repeat interval to schedule an Alarmee every hour, day, week, month, or year.
For instance, to schedule an alarm to repeat every day at 9:30 AM, you can use RepeatInterval.Daily:
localService.schedule(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've scheduled a daily repeating Alarmee!",
notificationBody = "This notification will be displayed every day at 09:30.",
scheduledDateTime = LocalDateTime(year = 2025, month = Month.JANUARY, dayOfMonth = 12, hour = 9, minute = 30),
repeatInterval = RepeatInterval.Daily, // Will repeat every day
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.DEFAULT,
channelId = "dailyNewsChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)You can also set a custom repeat interval using RepeatInterval.Custom(duration) to schedule an Alarmee at a specified duration interval.
For example, to schedule an alarm to repeat every 15 minutes, you can use RepeatInterval.Custom(duration = 15.minutes):
localService.schedule(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've scheduled a custom repeating Alarmee!",
notificationBody = "This notification will be displayed every 15 minutes",
repeatInterval = RepeatInterval.Custom(duration = 15.minutes), // Will repeat every 15 minutes
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.DEFAULT,
channelId = "otherChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)An alarm can be cancelled using its uuid, using Alarmee#cancel(...). If an alarm with the specified uuid is found, it will be canceled, preventing any future notifications from being triggered for that alarm.
localService.cancel(uuid = "myAlarmId")You can cancel all scheduled alarms at once using cancelAll(). This will cancel all pending alarms that have been scheduled through this service.
localService.cancelAll()[!NOTE] On Android, this cancels all displayed notifications. Due to AlarmManager API limitations, scheduled alarms cannot be canceled in bulk - they must be canceled individually using their specific identifiers.
You can trigger an alarm to instantly display a notification without scheduling it for a specific time:
localService.immediate(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've pushed an Alarmee right now!",
notificationBody = "This notification will be displayed right away",
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.DEFAULT,
channelId = "immediateChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)You can customize the notification sound on both Android and iOS.
[!WARNING] Custom sounds must be under 30 seconds in length on both Android and iOS. If the sound exceeds this limit, the system will fall back to the default notification sound..
Notification sounds are set via AlarmeeNotificationChannel, which allows you to define the sound file for a specific notification channel.
res/raw directory of your app (e.g., res/raw/notifications_sound.obb).AlarmeeNotificationChannel(
id = "dailyNewsChannelId",
name = "Daily news notifications",
importance = NotificationManager.IMPORTANCE_HIGH,
soundFilename = "notifications_sound", // file name without the extension
)Notification sounds are set in the IosNotificationConfiguration by providing the file name of the sound located in the app's bundle.
main bundle.Alarmee(
// ...
iosNotificationConfiguration = IosNotificationConfiguration(
soundFilename = "notifications_sound.wav",
),
)AlarmeeAndroidPlatformConfiguration(
notificationIconResId = R.drawable.ic_notification,
notificationIconColor = Color.Yellow,
// ...
)localService.schedule(
alarmee = Alarmee(
androidNotificationConfiguration = AndroidNotificationConfiguration(
notificationIconResId = R.drawable.ic_another_notification,
notificationIconColor = Color.Red,
// ...
),
// ...
)
)On iOS, customizing icon colors and drawables is not supported.
On Android, badge numbers are managed by the system and direct control over the badge number is not available in the notification API. The system automatically handles badge updates based on notifications.
You can customize the badge number displayed on the app icon for notifications. This is done using the IosNotificationConfiguration:
Alarmee(
// ...
iosNotificationConfiguration = IosNotificationConfiguration(
badge = 4,
),
)If badge = 0, the badge will be cleared from the app icon. If badge = null, the badge will not be updated.
You can add up to 3 action buttons to your notifications. When a user taps an action button, your app receives a callback with the action ID.
Adding action buttons to a notification:
localService.immediate(
alarmee = Alarmee(
uuid = "messageNotificationId",
notificationTitle = "π© New Message",
notificationBody = "You have a new message!",
actions = listOf(
NotificationAction(id = "reply", label = "Reply"),
NotificationAction(id = "mark_read", label = "Mark as Read"),
NotificationAction(id = "dismiss", label = "Dismiss"),
),
androidNotificationConfiguration = AndroidNotificationConfiguration(
priority = AndroidNotificationPriority.HIGH,
channelId = "messagesChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)Handling action button clicks:
To receive callbacks when a user taps an action button, register a callback using the onActionClicked extension function:
import com.tweener.alarmee.onActionClicked
// Register the callback early in your app lifecycle
alarmService.local.onActionClicked { event ->
println("Action clicked: ${event.actionId} on notification ${event.notificationUuid}")
when (event.actionId) {
"reply" -> { /* Handle reply action */ }
"mark_read" -> { /* Handle mark as read action */ }
"dismiss" -> { /* Handle dismiss action */ }
}
}[!IMPORTANT]
- Only one callback can be registered at a time. Subsequent calls replace the previous callback.
- Register the callback early in your app lifecycle (e.g., in
Application.onCreate()on Android or aLaunchedEffectin your root composable).- On Android, the notification is automatically dismissed when an action button is tapped.
On iOS, you need to update your AppDelegate.swift to forward action button taps to the Alarmee callback registry:
import alarmee
// In your AppDelegate class that conforms to UNUserNotificationCenterDelegate:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
let actionIdentifier = response.actionIdentifier
// Handle notification action button taps
if actionIdentifier != UNNotificationDefaultActionIdentifier && actionIdentifier != UNNotificationDismissActionIdentifier {
if let notificationUuid = userInfo["notificationUuid"] as? String {
NotificationActionHelper().onActionClicked(notificationUuid: notificationUuid, actionId: actionIdentifier)
}
}
completionHandler()
}[!WARNING] On iOS, make sure to add Firebase as a dependency (
https://github.com/firebase/firebase-ios-sdk) to your Xcode project.Then, in your target, add
Background Modes(checkRemote notifications) andPush notificationscapabilities.
The PushNotificationService handles push notifications for mobile platforms only (Android & iOS). It is available via the MobileAlarmeeService interface.
You can access it like this:
val pushService = alarmService.pushThis is only available on Android and iOS. On other targets, pushService will be null.
You can retrieve the Firebase Installation ID for the current app instance:
val installationIdResult = pushService.getInstallationId()
installationIdResult.onSuccess { installationId ->
println("Firebase Installation ID: $installationId")
}.onFailure { error ->
println("Failed to get Installation ID: $error")
}The Installation ID uniquely identifies the app installation on the device and is useful for analytics, targeting specific devices, or debugging.
Firebase Cloud Messaging (FCM) tokens can change when:
You can retrieve the current FCM token:
val tokenResult = pushService.getToken()
tokenResult.onSuccess { token ->
println("FCM Token: $token")
// Send token to your server
}.onFailure { error ->
println("Failed to get token: $error")
}To handle token updates, register a callback that will be notified whenever a new token is generated:
pushService.onNewToken { newToken ->
// Update your server with the new token
sendTokenToServer(newToken)
}You can also manually refresh the token for testing purposes:
pushService.forceTokenRefresh()To handle incoming push message payloads in your app (e.g., for custom processing, updating UI, or syncing data), register a callback that will be invoked whenever a push message is received:
pushService.onPushMessageReceived { payload ->
println("π© Push message received with payload: $payload")
// Handle custom logic here, e.g., update UI, sync data, etc.
}This callback receives the complete key-value payload from the push message, allowing you to access any custom data sent with the notification. The callback is triggered before the notification is displayed to the user.
Note: You can register multiple callbacks - all registered callbacks will be invoked when a push message is received.
To display images in push notifications on iOS, create a Notification Service Extension and paste the provided NotificationService.swift file:
AlarmeeNotificationService
NotificationService.swift
To send a push notification manually using Postman or curl, you can call the FCM v1 HTTP API with the following:
URL:
https://fcm.googleapis.com/v1/projects/{YOUR_FIREBASE_PROJECT_ID}/messages:send
Replace {YOUR_PROJECT_ID} with your Firebase project ID.
Example payload:
{
"message": {
"token": "DEVICE_FCM_TOKEN",
"apns": {
"payload": {
"aps": {
"alert": {
"title": "Title for iOS",
"body": "This is the body of the iOS notification"
},
"mutable-content": 1
}
},
"headers": {
"apns-priority": "10"
}
},
"data": {
"title": "Title for Android",
"body": "This is the body of the Android notification",
"deepLinkUri": "app://open/target", // Used on both Android & iOS
"imageUrl": "https://rickandmortyapi.com/api/character/avatar/1.jpeg" // Used on both Android & iOS
}
}
}token: the FCM token of the target device.apns.payload.aps.mutable-content: required for displaying images on iOS.data.imageUrl: optional parameter to display an image within the notification.apns.headers.apns-priority = 10 ensures the push is delivered immediately.To authenticate requests to the FCM HTTP v1 API, you must include a Bearer token in the Authorization header.
gcloud CLI installed and authenticated.gcloud config set project YOUR_FIREBASE_PROJECT_IDgcloud auth print-access-tokenAuthorization: Bearer YOUR_ACCESS_TOKENThat's it! Your push notifications will now support images on iOS.
Version 2.0 introduces a new API structure with a focus on clearer service boundaries and support for both local and push notifications. Follow these steps to migrate your existing code:
In 1.x, the entry point was:
val alarmeeScheduler: AlarmeeScheduler = rememberAlarmeeScheduler(
platformConfiguration = platformConfiguration
)In 2.0, it has been replaced by:
val alarmService: AlarmeeService = rememberAlarmeeService(
platformConfiguration = platformConfiguration
)All method calls on AlarmeeScheduler should now be redirected to the local notification service from AlarmeeService:
alarmeeScheduler.schedule(alarmee)alarmService.local.schedule(alarmee)Similarly, any other function calls (e.g., cancel(...), immediate(...), etc.) should follow this pattern:
// Before
alarmeeScheduler.cancel(alarmee)
// After
alarmService.local.cancel(alarmee)In 1.x, to instantly trigger a local notification, you called:
alarmeeScheduler.push(alarmee)In 2.0, this method has been renamed to:
alarmService.local.immediate(alarmee)| 1.x | 2.0 |
|---|---|
AlarmeeScheduler |
AlarmeeService |
rememberAlarmeeScheduler(...) |
rememberAlarmeeService(...) |
alarmeeScheduler.schedule(...) |
alarmService.local.schedule(...) |
alarmeeScheduler.cancel(...) |
alarmService.local.cancel(...) |
alarmeeScheduler.push(...) |
alarmService.local.immediate(...) |
We love your input and welcome any contributions! Please read our contribution guidelines before submitting a pull request.
Alarmee is licensed under the Apache-2.0.
Alarmee is a Kotlin/Compose Multiplatform library designed to simplify scheduling alarms and notifications on both Android and iOS platforms. With Alarmee, you can schedule one-time or repeating alarms, display platform-specific notifications, and now supports push notifications using Firebase Cloud Messaging (Android) and Apple Push Notification service (iOS).
[!WARNING] Upgrading from v1.x?
Check out the Migration Guide to update your code for version 2.0.
Be sure to show your support by starring βοΈ this repository, and feel free to contribute if you're interested!
Alarmee powers notifications in real-world apps:
settings.gradle.kts file, add Maven Central to your repositories:repositories {
mavenCentral()
}alarmee dependency.alarmee-push dependency.Open libs.versions.toml:
[versions]
alarmee = "2.4.0" // Check latest version
[libraries]
alarmee = { group = "io.github.tweener", name = "alarmee", version.ref = "alarmee" } // For local notifications only
alarmee = { group = "io.github.tweener", name = "alarmee-push", version.ref = "alarmee" } // For both local & push notifications
Then in your module build.gradle.kts add:
dependencies {
// Only one of these is needed, depending on your use case
implementation(libs.alarmee)
implementation(libs.alarmee.push)
}In your module build.gradle.kts add:
dependencies {
val alarmee_version = "2.4.0" // Check latest version
// Only one of these is needed, depending on your use case
implementation("io.github.tweener:alarmee:$alarmee_version") // For local notifications only
implementation("io.github.tweener:alarmee-push:$alarmee_version") // For both local & push notifications
}To get started with Alarmee, you need to provide a platform-specific configuration for Android and iOS. Follow these steps.
In your commonMain source set, declare the following function to provide platform-specific configuration:
expect fun createAlarmeePlatformConfiguration(): AlarmeePlatformConfigurationIn the androidMain source set, implement the actual function and return an AlarmeeAndroidPlatformConfiguration:
actual fun createAlarmeePlatformConfiguration(): AlarmeePlatformConfiguration =
AlarmeeAndroidPlatformConfiguration(
notificationIconResId = R.drawable.ic_notification,
notificationIconColor = androidx.compose.ui.graphics.Color.Red, // Defaults to Color.Transparent is not specified
useExactScheduling = true, // Enable exact alarm scheduling for more precise timing (Android 12+, requires SCHEDULE_EXACT_ALARM permission)
notificationChannels = listOf(
AlarmeeNotificationChannel(
id = "dailyNewsChannelId",
name = "Daily news notifications",
importance = NotificationManager.IMPORTANCE_HIGH,
soundFilename = "notifications_sound",
),
AlarmeeNotificationChannel(
id = "breakingNewsChannelId",
name = "Breaking news notifications",
importance = NotificationManager.IMPORTANCE_LOW,
),
// List all the notification channels you need here
)
)In the iosMain source set, implement the actual function and return an AlarmeeIosPlatformConfiguration:
val platformConfiguration: AlarmeePlatformConfiguration = AlarmeeIosPlatformConfigurationThere are multiple ways to initialize Alarmee depending on your setup.
With Compose:
val alarmService: AlarmeeService = rememberAlarmeeService(
platformConfiguration = createAlarmeePlatformConfiguration()
)Without Compose:
val alarmeeService = createAlarmeeService()
alarmeeService.initialize(platformConfiguration = createAlarmeePlatformConfiguration())Alarmee also supports push notifications on mobile via Firebase (Android) or APNs (iOS). If you're already using Firebase in your app, you can pass your own Firebase instance to avoid initializing it twice.
With Compose:
val alarmService: MobileAlarmeeService = rememberAlarmeeMobileService(
platformConfiguration = createAlarmeePlatformConfiguration()
)val alarmService: MobileAlarmeeService = rememberAlarmeeMobileService(
platformConfiguration = createAlarmeePlatformConfiguration(),
firebase = Firebase
)Without Compose:
val alarmeeService = createAlarmeeMobileService()
alarmeeService.initialize(platformConfiguration = createAlarmeePlatformConfiguration())val alarmeeService = createAlarmeeMobileService()
alarmeeService.initialize(
platformConfiguration = createAlarmeePlatformConfiguration(),
firebase = Firebase
)You can then use this instance to schedule or cancel alarms from your shared code.
[!IMPORTANT] Before using Alarmee, make sure the Notifications permission is granted on the target platform (Android official documentation, iOS official documentation).
Alternativally, you can use
moko-permissionsto easily handle permissions for you.
After initializing AlarmeeService, you can access the notification services:
To send local notifications, use the local service:
val localService = alarmService.local
localService.schedule(...) // For instanceThis is available on all targets (Android, iOS, desktop, web, etc.).
To access push notifications (e.g. Firebase):
val pushService = alarmService.pushThis is only available on Android and iOS. On non-mobile targets, pushService will be null.
You can schedule an alarm to be triggered at a specific time of the day, using AlarmeeService#schedule(...). When the alarm is triggered, a notification will be displayed.
For instance, to schedule an alarm on January 12th, 2025, at 5 PM:
localService.schedule(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've scheduled an Alarmee!",
notificationBody = "This is the notification that will be displayed at the specified date and time.",
scheduledDateTime = LocalDateTime(year = 2025, month = Month.JANUARY, dayOfMonth = 12, hour = 17, minute = 0),
deepLinkUri = "https://www.example.com", // A deep link URI to be retrieved in MainActivity#onNewIntent() on Android and in AppDelegate#userNotificationCenter() on iOS
imageUrl = "https://rickandmortyapi.com/api/character/avatar/1.jpeg", // Optional parameter to display an image within the notification
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.HIGH,
channelId = "dailyNewsChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)You can specify a RepeatInterval parameter, which allows scheduling an alarm to repeat hourly, daily, weekly, monthly, yearly or custom, based on the specified scheduledDateTime.
You can use a fixed repeat interval to schedule an Alarmee every hour, day, week, month, or year.
For instance, to schedule an alarm to repeat every day at 9:30 AM, you can use RepeatInterval.Daily:
localService.schedule(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've scheduled a daily repeating Alarmee!",
notificationBody = "This notification will be displayed every day at 09:30.",
scheduledDateTime = LocalDateTime(year = 2025, month = Month.JANUARY, dayOfMonth = 12, hour = 9, minute = 30),
repeatInterval = RepeatInterval.Daily, // Will repeat every day
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.DEFAULT,
channelId = "dailyNewsChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)You can also set a custom repeat interval using RepeatInterval.Custom(duration) to schedule an Alarmee at a specified duration interval.
For example, to schedule an alarm to repeat every 15 minutes, you can use RepeatInterval.Custom(duration = 15.minutes):
localService.schedule(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've scheduled a custom repeating Alarmee!",
notificationBody = "This notification will be displayed every 15 minutes",
repeatInterval = RepeatInterval.Custom(duration = 15.minutes), // Will repeat every 15 minutes
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.DEFAULT,
channelId = "otherChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)An alarm can be cancelled using its uuid, using Alarmee#cancel(...). If an alarm with the specified uuid is found, it will be canceled, preventing any future notifications from being triggered for that alarm.
localService.cancel(uuid = "myAlarmId")You can cancel all scheduled alarms at once using cancelAll(). This will cancel all pending alarms that have been scheduled through this service.
localService.cancelAll()[!NOTE] On Android, this cancels all displayed notifications. Due to AlarmManager API limitations, scheduled alarms cannot be canceled in bulk - they must be canceled individually using their specific identifiers.
You can trigger an alarm to instantly display a notification without scheduling it for a specific time:
localService.immediate(
alarmee = Alarmee(
uuid = "myAlarmId",
notificationTitle = "π Congratulations! You've pushed an Alarmee right now!",
notificationBody = "This notification will be displayed right away",
androidNotificationConfiguration = AndroidNotificationConfiguration( // Required configuration for Android target only (this parameter is ignored on iOS)
priority = AndroidNotificationPriority.DEFAULT,
channelId = "immediateChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)You can customize the notification sound on both Android and iOS.
[!WARNING] Custom sounds must be under 30 seconds in length on both Android and iOS. If the sound exceeds this limit, the system will fall back to the default notification sound..
Notification sounds are set via AlarmeeNotificationChannel, which allows you to define the sound file for a specific notification channel.
res/raw directory of your app (e.g., res/raw/notifications_sound.obb).AlarmeeNotificationChannel(
id = "dailyNewsChannelId",
name = "Daily news notifications",
importance = NotificationManager.IMPORTANCE_HIGH,
soundFilename = "notifications_sound", // file name without the extension
)Notification sounds are set in the IosNotificationConfiguration by providing the file name of the sound located in the app's bundle.
main bundle.Alarmee(
// ...
iosNotificationConfiguration = IosNotificationConfiguration(
soundFilename = "notifications_sound.wav",
),
)AlarmeeAndroidPlatformConfiguration(
notificationIconResId = R.drawable.ic_notification,
notificationIconColor = Color.Yellow,
// ...
)localService.schedule(
alarmee = Alarmee(
androidNotificationConfiguration = AndroidNotificationConfiguration(
notificationIconResId = R.drawable.ic_another_notification,
notificationIconColor = Color.Red,
// ...
),
// ...
)
)On iOS, customizing icon colors and drawables is not supported.
On Android, badge numbers are managed by the system and direct control over the badge number is not available in the notification API. The system automatically handles badge updates based on notifications.
You can customize the badge number displayed on the app icon for notifications. This is done using the IosNotificationConfiguration:
Alarmee(
// ...
iosNotificationConfiguration = IosNotificationConfiguration(
badge = 4,
),
)If badge = 0, the badge will be cleared from the app icon. If badge = null, the badge will not be updated.
You can add up to 3 action buttons to your notifications. When a user taps an action button, your app receives a callback with the action ID.
Adding action buttons to a notification:
localService.immediate(
alarmee = Alarmee(
uuid = "messageNotificationId",
notificationTitle = "π© New Message",
notificationBody = "You have a new message!",
actions = listOf(
NotificationAction(id = "reply", label = "Reply"),
NotificationAction(id = "mark_read", label = "Mark as Read"),
NotificationAction(id = "dismiss", label = "Dismiss"),
),
androidNotificationConfiguration = AndroidNotificationConfiguration(
priority = AndroidNotificationPriority.HIGH,
channelId = "messagesChannelId",
),
iosNotificationConfiguration = IosNotificationConfiguration(),
)
)Handling action button clicks:
To receive callbacks when a user taps an action button, register a callback using the onActionClicked extension function:
import com.tweener.alarmee.onActionClicked
// Register the callback early in your app lifecycle
alarmService.local.onActionClicked { event ->
println("Action clicked: ${event.actionId} on notification ${event.notificationUuid}")
when (event.actionId) {
"reply" -> { /* Handle reply action */ }
"mark_read" -> { /* Handle mark as read action */ }
"dismiss" -> { /* Handle dismiss action */ }
}
}[!IMPORTANT]
- Only one callback can be registered at a time. Subsequent calls replace the previous callback.
- Register the callback early in your app lifecycle (e.g., in
Application.onCreate()on Android or aLaunchedEffectin your root composable).- On Android, the notification is automatically dismissed when an action button is tapped.
On iOS, you need to update your AppDelegate.swift to forward action button taps to the Alarmee callback registry:
import alarmee
// In your AppDelegate class that conforms to UNUserNotificationCenterDelegate:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
let actionIdentifier = response.actionIdentifier
// Handle notification action button taps
if actionIdentifier != UNNotificationDefaultActionIdentifier && actionIdentifier != UNNotificationDismissActionIdentifier {
if let notificationUuid = userInfo["notificationUuid"] as? String {
NotificationActionHelper().onActionClicked(notificationUuid: notificationUuid, actionId: actionIdentifier)
}
}
completionHandler()
}[!WARNING] On iOS, make sure to add Firebase as a dependency (
https://github.com/firebase/firebase-ios-sdk) to your Xcode project.Then, in your target, add
Background Modes(checkRemote notifications) andPush notificationscapabilities.
The PushNotificationService handles push notifications for mobile platforms only (Android & iOS). It is available via the MobileAlarmeeService interface.
You can access it like this:
val pushService = alarmService.pushThis is only available on Android and iOS. On other targets, pushService will be null.
You can retrieve the Firebase Installation ID for the current app instance:
val installationIdResult = pushService.getInstallationId()
installationIdResult.onSuccess { installationId ->
println("Firebase Installation ID: $installationId")
}.onFailure { error ->
println("Failed to get Installation ID: $error")
}The Installation ID uniquely identifies the app installation on the device and is useful for analytics, targeting specific devices, or debugging.
Firebase Cloud Messaging (FCM) tokens can change when:
You can retrieve the current FCM token:
val tokenResult = pushService.getToken()
tokenResult.onSuccess { token ->
println("FCM Token: $token")
// Send token to your server
}.onFailure { error ->
println("Failed to get token: $error")
}To handle token updates, register a callback that will be notified whenever a new token is generated:
pushService.onNewToken { newToken ->
// Update your server with the new token
sendTokenToServer(newToken)
}You can also manually refresh the token for testing purposes:
pushService.forceTokenRefresh()To handle incoming push message payloads in your app (e.g., for custom processing, updating UI, or syncing data), register a callback that will be invoked whenever a push message is received:
pushService.onPushMessageReceived { payload ->
println("π© Push message received with payload: $payload")
// Handle custom logic here, e.g., update UI, sync data, etc.
}This callback receives the complete key-value payload from the push message, allowing you to access any custom data sent with the notification. The callback is triggered before the notification is displayed to the user.
Note: You can register multiple callbacks - all registered callbacks will be invoked when a push message is received.
To display images in push notifications on iOS, create a Notification Service Extension and paste the provided NotificationService.swift file:
AlarmeeNotificationService
NotificationService.swift
To send a push notification manually using Postman or curl, you can call the FCM v1 HTTP API with the following:
URL:
https://fcm.googleapis.com/v1/projects/{YOUR_FIREBASE_PROJECT_ID}/messages:send
Replace {YOUR_PROJECT_ID} with your Firebase project ID.
Example payload:
{
"message": {
"token": "DEVICE_FCM_TOKEN",
"apns": {
"payload": {
"aps": {
"alert": {
"title": "Title for iOS",
"body": "This is the body of the iOS notification"
},
"mutable-content": 1
}
},
"headers": {
"apns-priority": "10"
}
},
"data": {
"title": "Title for Android",
"body": "This is the body of the Android notification",
"deepLinkUri": "app://open/target", // Used on both Android & iOS
"imageUrl": "https://rickandmortyapi.com/api/character/avatar/1.jpeg" // Used on both Android & iOS
}
}
}token: the FCM token of the target device.apns.payload.aps.mutable-content: required for displaying images on iOS.data.imageUrl: optional parameter to display an image within the notification.apns.headers.apns-priority = 10 ensures the push is delivered immediately.To authenticate requests to the FCM HTTP v1 API, you must include a Bearer token in the Authorization header.
gcloud CLI installed and authenticated.gcloud config set project YOUR_FIREBASE_PROJECT_IDgcloud auth print-access-tokenAuthorization: Bearer YOUR_ACCESS_TOKENThat's it! Your push notifications will now support images on iOS.
Version 2.0 introduces a new API structure with a focus on clearer service boundaries and support for both local and push notifications. Follow these steps to migrate your existing code:
In 1.x, the entry point was:
val alarmeeScheduler: AlarmeeScheduler = rememberAlarmeeScheduler(
platformConfiguration = platformConfiguration
)In 2.0, it has been replaced by:
val alarmService: AlarmeeService = rememberAlarmeeService(
platformConfiguration = platformConfiguration
)All method calls on AlarmeeScheduler should now be redirected to the local notification service from AlarmeeService:
alarmeeScheduler.schedule(alarmee)alarmService.local.schedule(alarmee)Similarly, any other function calls (e.g., cancel(...), immediate(...), etc.) should follow this pattern:
// Before
alarmeeScheduler.cancel(alarmee)
// After
alarmService.local.cancel(alarmee)In 1.x, to instantly trigger a local notification, you called:
alarmeeScheduler.push(alarmee)In 2.0, this method has been renamed to:
alarmService.local.immediate(alarmee)| 1.x | 2.0 |
|---|---|
AlarmeeScheduler |
AlarmeeService |
rememberAlarmeeScheduler(...) |
rememberAlarmeeService(...) |
alarmeeScheduler.schedule(...) |
alarmService.local.schedule(...) |
alarmeeScheduler.cancel(...) |
alarmService.local.cancel(...) |
alarmeeScheduler.push(...) |
alarmService.local.immediate(...) |
We love your input and welcome any contributions! Please read our contribution guidelines before submitting a pull request.
Alarmee is licensed under the Apache-2.0.