Skip to content

Commit 7901c0c

Browse files
Richardclaude
andcommitted
feat: initial package scaffold for @rick427/background-timer
Full New Architecture (Turbo Module + JSI) React Native background timer package targeting iOS 13+ and Android 8+ (API 26). Core features: - Foreground Service + AlarmManager (Android) — survives Doze mode - BGTaskScheduler + UIBackgroundTask layered strategy (iOS 13+) - Wall-clock drift correction on both platforms - Timer persistence across app kills (SharedPreferences / UserDefaults) - Boot receiver reconciles timers after device reboot (Android) - Rich imperative API: create/start/pause/resume/stop/reset/destroy - Drop-in legacy API: setTimeout / setInterval / clearTimeout / clearInterval - useBackgroundTimer() React hook with stable callback refs - Fully typed — all public surface in src/types.ts - Expo config plugin (BGTaskSchedulerPermittedIdentifiers + UIBackgroundModes) - Kotlin (not Java) + Swift (not ObjC) native implementations - Named string timer IDs, customizable Android notifications Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0 parents  commit 7901c0c

28 files changed

Lines changed: 3265 additions & 0 deletions

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
node_modules/
2+
lib/
3+
android/build/
4+
ios/build/
5+
ios/Pods/
6+
*.xcworkspace
7+
.DS_Store
8+
*.log

android/build.gradle

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
buildscript {
2+
repositories {
3+
google()
4+
mavenCentral()
5+
}
6+
dependencies {
7+
classpath "com.android.tools.build:gradle:8.3.0"
8+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22"
9+
}
10+
}
11+
12+
apply plugin: "com.android.library"
13+
apply plugin: "kotlin-android"
14+
apply plugin: "com.facebook.react"
15+
16+
// ── Namespace (AGP 8+ requirement — no longer read from AndroidManifest) ─────
17+
android {
18+
namespace "com.rick427.backgroundtimer"
19+
compileSdk 34
20+
21+
defaultConfig {
22+
minSdk 26 // Android 8.0 (Foreground Services, notification channels)
23+
targetSdk 34
24+
}
25+
26+
compileOptions {
27+
sourceCompatibility JavaVersion.VERSION_17
28+
targetCompatibility JavaVersion.VERSION_17
29+
}
30+
31+
kotlinOptions {
32+
jvmTarget = "17"
33+
}
34+
35+
buildFeatures {
36+
buildConfig false
37+
}
38+
}
39+
40+
repositories {
41+
mavenCentral()
42+
google()
43+
}
44+
45+
dependencies {
46+
// React Native — provided by the host app, not bundled
47+
implementation "com.facebook.react:react-android"
48+
49+
// Kotlin standard library
50+
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.22"
51+
52+
// AndroidX Core (NotificationCompat)
53+
implementation "androidx.core:core-ktx:1.12.0"
54+
55+
// WorkManager — optional, used for deferred restarts after boot
56+
implementation "androidx.work:work-runtime-ktx:2.9.0"
57+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<!-- ── Permissions ─────────────────────────────────────────────────────── -->
5+
6+
<!--
7+
Required for foreground services (Android 9+).
8+
Declared automatically by this library.
9+
-->
10+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
11+
12+
<!--
13+
Required for timers to survive screen lock (Android 14+: type declaration).
14+
-->
15+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
16+
17+
<!--
18+
Keeps the CPU awake between alarm fire and service start.
19+
Released by the service immediately after processing.
20+
-->
21+
<uses-permission android:name="android.permission.WAKE_LOCK" />
22+
23+
<!--
24+
Android 12+ exact alarm permission (SCHEDULE_EXACT_ALARM).
25+
NOTE: This permission requires the user to grant it manually in
26+
Settings > Apps > Special app access > Alarms & reminders.
27+
The app must call BackgroundTimer.requestExactAlarmPermission() to guide
28+
the user there. Without this, we fall back to inexact alarms.
29+
-->
30+
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
31+
32+
<!--
33+
Restores persisted timers after a device reboot.
34+
Required for the persistence feature.
35+
-->
36+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
37+
38+
<!-- ── Components ─────────────────────────────────────────────────────── -->
39+
40+
<application>
41+
42+
<!--
43+
Foreground service that keeps timers alive.
44+
foregroundServiceType="specialUse" is required on Android 14+.
45+
stopWithTask=false: service keeps running even if user swipes away the app.
46+
-->
47+
<service
48+
android:name=".BackgroundTimerService"
49+
android:exported="false"
50+
android:foregroundServiceType="specialUse"
51+
android:stopWithTask="false">
52+
53+
<!-- Required for Android 14+ specialUse foreground service type -->
54+
<property
55+
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
56+
android:value="background_timer" />
57+
58+
</service>
59+
60+
<!--
61+
Catches AlarmManager broadcasts.
62+
exported=false: only our own PendingIntents can trigger this.
63+
-->
64+
<receiver
65+
android:name=".AlarmReceiver"
66+
android:exported="false">
67+
<intent-filter>
68+
<action android:name="com.rick427.backgroundtimer.ALARM" />
69+
</intent-filter>
70+
</receiver>
71+
72+
<!--
73+
Restores timers after device reboot.
74+
-->
75+
<receiver
76+
android:name=".BootReceiver"
77+
android:exported="true">
78+
<intent-filter>
79+
<action android:name="android.intent.action.BOOT_COMPLETED" />
80+
<!-- HTC / OnePlus fast-boot support -->
81+
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
82+
</intent-filter>
83+
</receiver>
84+
85+
</application>
86+
87+
</manifest>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.rick427.backgroundtimer
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.util.Log
7+
8+
/**
9+
* AlarmReceiver
10+
*
11+
* BroadcastReceiver that catches AlarmManager broadcasts.
12+
* Runs in the main process — forwards the alarm to BackgroundTimerService.
13+
*
14+
* Registered in AndroidManifest.xml with exported=false so only our
15+
* AlarmManager PendingIntents can trigger it.
16+
*/
17+
class AlarmReceiver : BroadcastReceiver() {
18+
19+
override fun onReceive(context: Context, intent: Intent) {
20+
if (intent.action != BackgroundTimerService.ACTION_ALARM) return
21+
22+
val timerId = intent.getStringExtra(BackgroundTimerService.EXTRA_TIMER_ID)
23+
if (timerId == null) {
24+
Log.w(TAG, "AlarmReceiver: missing timer ID")
25+
return
26+
}
27+
28+
Log.d(TAG, "Alarm received for timer: $timerId")
29+
30+
// Forward to the service — it manages all timer state
31+
val serviceIntent = Intent(context, BackgroundTimerService::class.java).apply {
32+
action = BackgroundTimerService.ACTION_ALARM
33+
putExtra(BackgroundTimerService.EXTRA_TIMER_ID, timerId)
34+
}
35+
36+
// Use startForegroundService on API 26+ since the service may not be bound
37+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
38+
context.startForegroundService(serviceIntent)
39+
} else {
40+
context.startService(serviceIntent)
41+
}
42+
}
43+
44+
companion object {
45+
private const val TAG = "BGTimerAlarmReceiver"
46+
}
47+
}

0 commit comments

Comments
 (0)