rn-native-alarmkit
v0.1.13
Published
Cross-platform native alarm scheduling for React Native with AlarmKit (iOS 26+) and AlarmManager (Android 12+) support, including smart fallbacks
Maintainers
Readme
rn-native-alarmkit
Cross-platform native alarm scheduling for React Native with automatic fallback handling.
Features
- ✨ Native System Alarms - Uses AlarmKit (iOS 26+) and AlarmManager (Android 12+)
- 🔔 Breaks Through Do Not Disturb - Native alarms bypass Focus/Silent modes
- 🎯 Exact Timing - Guaranteed alarm delivery at precise times
- 🔄 Smart Fallbacks - Automatically uses notifications on older platforms
- 📱 Full TypeScript Support - Complete type definitions
- 🎨 Customizable Actions - Snooze, dismiss, and custom buttons
- ⏰ Flexible Scheduling - One-time, recurring (daily/weekly), and interval-based alarms
- 📊 Comprehensive Management - Query, update, and cancel alarms with ease
Platform Support
| Platform | Capability | Version | Notes | |----------|-----------|---------|-------| | iOS 26+ | Native Alarms (AlarmKit) | iOS 26.0+ | Full system integration, Live Activities | | iOS < 26 | Local Notifications | iOS 13.0+ | May be silenced by Do Not Disturb | | Android 12+ (with permission) | Exact Alarms | API 31+ | Requires SCHEDULE_EXACT_ALARM permission | | Android 12+ (no permission) | Inexact Alarms | API 31+ | Timing may be off by several minutes | | Android < 12 | Exact Alarms | API 21+ | No permission required |
Installation
npm install rn-native-alarmkit
# or
yarn add rn-native-alarmkitiOS Setup
cd ios && pod installAdd the following to your Info.plist:
<key>NSAlarmKitUsageDescription</key>
<string>We need alarm access to remind you at exact times</string>Android Setup
Add to AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Exact alarm permission (Android 12+) -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Wake lock for alarms -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Boot receiver to reschedule alarms -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application>
<!-- Additional configuration will be added automatically -->
</application>
</manifest>Quick Start
import NativeAlarmManager from 'rn-native-alarmkit';
// Check capability
const capability = await NativeAlarmManager.checkCapability();
console.log('Using:', capability.capability);
// Request permission if needed
if (capability.requiresPermission && capability.canRequestPermission) {
const granted = await NativeAlarmManager.requestPermission();
if (!granted) {
// Handle permission denied
}
}
// Schedule a daily alarm
const alarm = await NativeAlarmManager.scheduleAlarm(
{
id: 'morning-alarm',
type: 'recurring',
time: { hour: 8, minute: 0 },
daysOfWeek: [1, 2, 3, 4, 5], // Monday-Friday
},
{
title: 'Good Morning!',
body: 'Time to start your day',
actions: [
{ id: 'dismiss', title: 'Dismiss', behavior: 'dismiss' },
{ id: 'snooze', title: 'Snooze 10m', behavior: 'snooze', snoozeDuration: 10 },
],
}
);
// Listen for alarm events
NativeAlarmManager.onAlarmFired((event) => {
console.log('Alarm fired:', event.alarm.config.title);
});Usage Examples
Medication Reminder (Daily at specific times)
await NativeAlarmManager.scheduleAlarm(
{
id: 'med-morning',
type: 'recurring',
time: { hour: 8, minute: 0 },
daysOfWeek: [0, 1, 2, 3, 4, 5, 6], // Every day
},
{
title: 'Take Morning Medication',
body: 'Lisinopril 10mg',
sound: 'medication_alert',
category: 'medications',
data: {
medicationId: 'med-001',
dosage: '10mg',
},
actions: [
{
id: 'taken',
title: 'Taken',
behavior: 'dismiss',
icon: 'checkmark.circle.fill', // iOS SF Symbol
},
{
id: 'snooze',
title: 'Snooze 10m',
behavior: 'snooze',
snoozeDuration: 10,
icon: 'clock.arrow.circlepath',
},
],
color: '#007AFF',
}
);Interval-Based Reminder (Every 4 hours)
await NativeAlarmManager.scheduleAlarm(
{
id: 'hydration-reminder',
type: 'interval',
intervalMinutes: 240, // 4 hours
},
{
title: 'Drink Water',
body: 'Stay hydrated!',
category: 'health',
}
);One-Time Alarm (Specific date/time)
await NativeAlarmManager.scheduleAlarm(
{
id: 'appointment',
type: 'fixed',
time: { hour: 14, minute: 30 },
date: new Date('2025-12-25'),
},
{
title: 'Doctor Appointment',
body: 'Appointment at 2:30 PM',
}
);Listening for Alarms
// Listen for alarm fired events
const unsubscribe = NativeAlarmManager.onAlarmFired((event) => {
console.log('Alarm fired:', event.alarm.id);
// Access custom data
if (event.alarm.config.data) {
const medicationId = event.alarm.config.data.medicationId;
// Update your app's state, log medication taken, etc.
}
// Check which action was taken
if (event.action) {
console.log('Action taken:', event.action.actionId);
}
});
// Later: cleanup
unsubscribe();
// Listen for permission changes
NativeAlarmManager.onPermissionChanged((event) => {
if (!event.granted) {
Alert.alert(
'Permission Required',
'Alarms may not fire reliably. Please enable in Settings.'
);
}
});Managing Alarms
// Get all alarms
const alarms = await NativeAlarmManager.getAllAlarms();
console.log(`${alarms.length} alarms scheduled`);
// Get alarms by category
const medAlarms = await NativeAlarmManager.getAlarmsByCategory('medications');
// Get specific alarm
const alarm = await NativeAlarmManager.getAlarm('morning-alarm');
// Update an alarm
await NativeAlarmManager.updateAlarm(
'morning-alarm',
{
id: 'morning-alarm',
type: 'recurring',
time: { hour: 9, minute: 0 }, // Changed time
daysOfWeek: [1, 2, 3, 4, 5],
},
{
title: 'Updated Morning Alarm',
}
);
// Cancel specific alarm
await NativeAlarmManager.cancelAlarm('morning-alarm');
// Cancel all alarms in category
await NativeAlarmManager.cancelAlarmsByCategory('medications');
// Cancel all alarms
await NativeAlarmManager.cancelAllAlarms();API Reference
checkCapability()
Returns information about alarm capabilities on the current device.
const capability = await NativeAlarmManager.checkCapability();Returns: AlarmCapabilityCheck
{
capability: 'native_alarms' | 'notification' | 'inexact' | 'none',
reason: string,
requiresPermission: boolean,
canRequestPermission: boolean,
platformDetails: {
platform: 'ios' | 'android',
version: number | string,
// iOS specific
alarmKitAvailable?: boolean,
alarmKitAuthStatus?: 'notDetermined' | 'authorized' | 'denied',
// Android specific
canScheduleExactAlarms?: boolean
}
}requestPermission()
Requests alarm permission from the user.
const granted = await NativeAlarmManager.requestPermission();Returns: Promise<boolean> - Whether permission was granted
Platform Notes:
- iOS 26+: Shows AlarmKit authorization dialog
- Android 12+: Opens system settings for SCHEDULE_EXACT_ALARM
- Other platforms: Returns
true(no permission needed)
scheduleAlarm(schedule, config)
Schedules a new alarm.
const alarm = await NativeAlarmManager.scheduleAlarm(schedule, config);Parameters:
schedule: AlarmSchedule
{
id: string, // Unique identifier
type: 'fixed' | 'recurring' | 'interval',
// For 'fixed' and 'recurring':
time?: {
hour: number, // 0-23
minute: number // 0-59
},
date?: Date, // For 'fixed' only
// For 'recurring':
daysOfWeek?: number[], // 0=Sunday, 6=Saturday
// For 'interval':
intervalMinutes?: number,
startTime?: Date
}config: AlarmConfig
{
title: string,
body?: string,
sound?: string, // 'default', 'none', or custom sound name
category?: string, // For grouping alarms
color?: string, // Hex color (e.g., '#007AFF')
data?: Record<string, any>, // Custom metadata
actions?: AlarmAction[]
}AlarmAction
{
id: string,
title: string,
behavior: 'dismiss' | 'snooze' | 'custom',
snoozeDuration?: number, // Minutes (for 'snooze')
icon?: string, // Platform-specific icon name
color?: string // Hex color
}Returns: Promise<ScheduledAlarm>
Event Listeners
onAlarmFired(callback)
Called when an alarm fires.
const unsubscribe = NativeAlarmManager.onAlarmFired((event) => {
// event.alarm - The alarm that fired
// event.firedAt - Actual fire time
// event.action - Action taken (if any)
});onPermissionChanged(callback)
Called when permission status changes.
const unsubscribe = NativeAlarmManager.onPermissionChanged((event) => {
// event.granted - Whether permission is granted
// event.capability - New capability level
// event.platform - 'ios' or 'android'
});Error Handling
import { AlarmError, AlarmErrorCode } from 'rn-native-alarmkit';
try {
await NativeAlarmManager.scheduleAlarm(schedule, config);
} catch (error) {
if (error instanceof AlarmError) {
switch (error.code) {
case AlarmErrorCode.PERMISSION_DENIED:
// Handle permission denied
break;
case AlarmErrorCode.INVALID_SCHEDULE:
// Handle invalid schedule
break;
case AlarmErrorCode.SYSTEM_ERROR:
// Handle system error
break;
}
}
}Best Practices
1. Always Check Capability First
const capability = await NativeAlarmManager.checkCapability();
if (capability.capability === 'none') {
// Show error to user
Alert.alert('Alarms Not Supported', 'Your device does not support reliable alarms');
return;
}
if (capability.capability === 'inexact') {
// Warn user about timing accuracy
Alert.alert(
'Limited Accuracy',
'Alarms may not fire at exact times. Grant permission for exact alarms in Settings.'
);
}2. Request Permission Before Scheduling
if (capability.requiresPermission && capability.canRequestPermission) {
const granted = await NativeAlarmManager.requestPermission();
if (!granted) {
// Explain why permission is needed
Alert.alert(
'Permission Required',
'We need alarm permission to remind you at exact times for medication.'
);
return;
}
}3. Use Categories for Organization
// Schedule all medication alarms with same category
await NativeAlarmManager.scheduleAlarm(
{ id: 'med-1', ... },
{ title: 'Med 1', category: 'medications' }
);
// Later: cancel all medication alarms at once
await NativeAlarmManager.cancelAlarmsByCategory('medications');4. Store Alarm IDs
// Store alarm ID in your local database
const alarm = await NativeAlarmManager.scheduleAlarm(schedule, config);
await database.execute(
'INSERT INTO alarms (id, medication_id) VALUES (?, ?)',
[alarm.id, medicationId]
);5. Handle Platform Differences
const actions: AlarmAction[] = [
{
id: 'taken',
title: 'Taken',
behavior: 'dismiss',
icon: Platform.select({
ios: 'checkmark.circle.fill', // SF Symbol
android: 'ic_check_circle' // Material Icon
})
}
];Troubleshooting
iOS: Alarms Not Firing
- Check iOS version:
Settings > General > About > Software Version(need iOS 26+) - Verify permission: Check capability status
- Check notification settings:
Settings > Notifications > [Your App]
Android: Alarms Not Firing
- Check battery optimization:
Settings > Apps > [Your App] > Battery > Unrestricted - Verify permission:
const capability = await NativeAlarmManager.checkCapability(); console.log(capability.platformDetails.canScheduleExactAlarms); - Grant exact alarm permission:
Settings > Apps > Special Access > Alarms & reminders
Alarms Not Persisting After Reboot
Android alarms are cleared on reboot. Implement a boot receiver to reschedule:
// In AndroidManifest.xml (already included if you followed setup)
<receiver android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>// In your app initialization
if (Platform.OS === 'android') {
const alarms = await NativeAlarmManager.getAllAlarms();
// Reschedule all alarms
for (const alarm of alarms) {
await NativeAlarmManager.updateAlarm(
alarm.id,
alarm.schedule,
alarm.config
);
}
}Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
License
MIT © [Your Name]
Related Documentation
Support
If you encounter any issues or have questions:
- Check the troubleshooting section
- Review the API documentation
- Open an issue on GitHub
