react-native-step-tracker-pro 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -0
  3. package/android/build.gradle +119 -0
  4. package/android/consumer-rules.pro +13 -0
  5. package/android/gradle.properties +3 -0
  6. package/android/src/main/AndroidManifest.xml +78 -0
  7. package/android/src/main/java/com/steptrackerpro/StepTrackerProModule.kt +598 -0
  8. package/android/src/main/java/com/steptrackerpro/StepTrackerProPackage.kt +26 -0
  9. package/android/src/main/java/com/steptrackerpro/core/DateKeys.kt +90 -0
  10. package/android/src/main/java/com/steptrackerpro/core/GoalTracker.kt +66 -0
  11. package/android/src/main/java/com/steptrackerpro/core/MetricsCalculator.kt +48 -0
  12. package/android/src/main/java/com/steptrackerpro/core/Models.kt +73 -0
  13. package/android/src/main/java/com/steptrackerpro/core/StepCounterEngine.kt +338 -0
  14. package/android/src/main/java/com/steptrackerpro/core/StepStateStore.kt +137 -0
  15. package/android/src/main/java/com/steptrackerpro/core/StepTrackerConfig.kt +184 -0
  16. package/android/src/main/java/com/steptrackerpro/core/StepTrackerCore.kt +280 -0
  17. package/android/src/main/java/com/steptrackerpro/db/Entities.kt +67 -0
  18. package/android/src/main/java/com/steptrackerpro/db/StepDao.kt +118 -0
  19. package/android/src/main/java/com/steptrackerpro/db/StepDatabase.kt +60 -0
  20. package/android/src/main/java/com/steptrackerpro/db/StepRepository.kt +146 -0
  21. package/android/src/main/java/com/steptrackerpro/health/HealthConnectManager.kt +198 -0
  22. package/android/src/main/java/com/steptrackerpro/health/HealthPermissionActivity.kt +99 -0
  23. package/android/src/main/java/com/steptrackerpro/service/BootReceiver.kt +54 -0
  24. package/android/src/main/java/com/steptrackerpro/service/NotificationActionReceiver.kt +17 -0
  25. package/android/src/main/java/com/steptrackerpro/service/NotificationFactory.kt +170 -0
  26. package/android/src/main/java/com/steptrackerpro/service/ServiceCommands.kt +42 -0
  27. package/android/src/main/java/com/steptrackerpro/service/StepTrackerService.kt +409 -0
  28. package/android/src/main/java/com/steptrackerpro/sync/SyncScheduler.kt +102 -0
  29. package/android/src/main/java/com/steptrackerpro/sync/SyncWorkers.kt +175 -0
  30. package/android/src/main/java/com/steptrackerpro/util/BatteryOptimizationHelper.kt +94 -0
  31. package/android/src/main/java/com/steptrackerpro/util/Bridge.kt +149 -0
  32. package/android/src/main/java/com/steptrackerpro/util/PermissionHelper.kt +92 -0
  33. package/android/src/main/java/com/steptrackerpro/util/StepEventBus.kt +53 -0
  34. package/android/src/main/res/drawable/stp_ic_steps.xml +9 -0
  35. package/android/src/main/res/values/strings.xml +9 -0
  36. package/android/src/main/res/values/styles.xml +8 -0
  37. package/android/src/newarch/java/com/steptrackerpro/StepTrackerProSpec.kt +10 -0
  38. package/android/src/oldarch/java/com/steptrackerpro/StepTrackerProSpec.kt +60 -0
  39. package/docs/API.md +329 -0
  40. package/docs/ARCHITECTURE.md +203 -0
  41. package/docs/INSTALLATION.md +175 -0
  42. package/docs/PLAY_STORE_COMPLIANCE.md +122 -0
  43. package/docs/TESTING.md +312 -0
  44. package/docs/TROUBLESHOOTING.md +96 -0
  45. package/lib/commonjs/NativeStepTrackerPro.js +14 -0
  46. package/lib/commonjs/NativeStepTrackerPro.js.map +1 -0
  47. package/lib/commonjs/StepTracker.js +290 -0
  48. package/lib/commonjs/StepTracker.js.map +1 -0
  49. package/lib/commonjs/constants.js +30 -0
  50. package/lib/commonjs/constants.js.map +1 -0
  51. package/lib/commonjs/errors.js +24 -0
  52. package/lib/commonjs/errors.js.map +1 -0
  53. package/lib/commonjs/hooks/index.js +20 -0
  54. package/lib/commonjs/hooks/index.js.map +1 -0
  55. package/lib/commonjs/hooks/useStepStats.js +51 -0
  56. package/lib/commonjs/hooks/useStepStats.js.map +1 -0
  57. package/lib/commonjs/hooks/useStepTracker.js +134 -0
  58. package/lib/commonjs/hooks/useStepTracker.js.map +1 -0
  59. package/lib/commonjs/index.js +64 -0
  60. package/lib/commonjs/index.js.map +1 -0
  61. package/lib/commonjs/package.json +1 -0
  62. package/lib/commonjs/types.js +2 -0
  63. package/lib/commonjs/types.js.map +1 -0
  64. package/lib/module/NativeStepTrackerPro.js +12 -0
  65. package/lib/module/NativeStepTrackerPro.js.map +1 -0
  66. package/lib/module/StepTracker.js +284 -0
  67. package/lib/module/StepTracker.js.map +1 -0
  68. package/lib/module/constants.js +26 -0
  69. package/lib/module/constants.js.map +1 -0
  70. package/lib/module/errors.js +18 -0
  71. package/lib/module/errors.js.map +1 -0
  72. package/lib/module/hooks/index.js +5 -0
  73. package/lib/module/hooks/index.js.map +1 -0
  74. package/lib/module/hooks/useStepStats.js +46 -0
  75. package/lib/module/hooks/useStepStats.js.map +1 -0
  76. package/lib/module/hooks/useStepTracker.js +129 -0
  77. package/lib/module/hooks/useStepTracker.js.map +1 -0
  78. package/lib/module/index.js +7 -0
  79. package/lib/module/index.js.map +1 -0
  80. package/lib/module/package.json +1 -0
  81. package/lib/module/types.js +2 -0
  82. package/lib/module/types.js.map +1 -0
  83. package/lib/typescript/NativeStepTrackerPro.d.ts +50 -0
  84. package/lib/typescript/NativeStepTrackerPro.d.ts.map +1 -0
  85. package/lib/typescript/StepTracker.d.ts +76 -0
  86. package/lib/typescript/StepTracker.d.ts.map +1 -0
  87. package/lib/typescript/constants.d.ts +10 -0
  88. package/lib/typescript/constants.d.ts.map +1 -0
  89. package/lib/typescript/errors.d.ts +8 -0
  90. package/lib/typescript/errors.d.ts.map +1 -0
  91. package/lib/typescript/hooks/index.d.ts +5 -0
  92. package/lib/typescript/hooks/index.d.ts.map +1 -0
  93. package/lib/typescript/hooks/useStepStats.d.ts +11 -0
  94. package/lib/typescript/hooks/useStepStats.d.ts.map +1 -0
  95. package/lib/typescript/hooks/useStepTracker.d.ts +26 -0
  96. package/lib/typescript/hooks/useStepTracker.d.ts.map +1 -0
  97. package/lib/typescript/index.d.ts +7 -0
  98. package/lib/typescript/index.d.ts.map +1 -0
  99. package/lib/typescript/types.d.ts +183 -0
  100. package/lib/typescript/types.d.ts.map +1 -0
  101. package/package.json +79 -0
  102. package/src/NativeStepTrackerPro.ts +66 -0
  103. package/src/StepTracker.ts +410 -0
  104. package/src/constants.ts +43 -0
  105. package/src/errors.ts +31 -0
  106. package/src/hooks/index.ts +4 -0
  107. package/src/hooks/useStepStats.ts +55 -0
  108. package/src/hooks/useStepTracker.ts +167 -0
  109. package/src/index.ts +27 -0
  110. package/src/types.ts +203 -0
@@ -0,0 +1,175 @@
1
+ # Installation
2
+
3
+ ## Requirements
4
+
5
+ | | |
6
+ |---|---|
7
+ | React Native | ≥ 0.74 (old and new architecture both supported) |
8
+ | `minSdkVersion` | 26 — `java.time` and the Health Connect client both need it |
9
+ | `compileSdkVersion` / `targetSdkVersion` | 35 |
10
+ | Java | 17 |
11
+ | Kotlin | 1.9.24 by default; see below for 2.x |
12
+
13
+ ## 1. Install
14
+
15
+ ```sh
16
+ npm install react-native-step-tracker-pro
17
+ # or
18
+ yarn add react-native-step-tracker-pro
19
+ ```
20
+
21
+ Autolinking picks the package up. No manual `MainApplication` edit is needed.
22
+
23
+ ## 2. Set the SDK levels
24
+
25
+ `android/build.gradle` in your app:
26
+
27
+ ```gradle
28
+ buildscript {
29
+ ext {
30
+ minSdkVersion = 26
31
+ compileSdkVersion = 35
32
+ targetSdkVersion = 35
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## 3. Kotlin 2.x (only if your app is already on it)
38
+
39
+ The library ships Room via KSP and defaults to Kotlin 1.9.24. KSP versions are
40
+ pinned to an exact Kotlin version, so if your app uses Kotlin 2.x you must say
41
+ so, or the build fails with a KSP/Kotlin mismatch.
42
+
43
+ ```gradle
44
+ buildscript {
45
+ ext {
46
+ kotlinVersion = "2.0.21"
47
+ kspVersion = "2.0.21-1.0.25" // must match kotlinVersion exactly
48
+ roomVersion = "2.7.1"
49
+ }
50
+ }
51
+ ```
52
+
53
+ Other overridable properties: `healthConnectVersion` (default `1.1.0-beta01`),
54
+ `workVersion` (default `2.9.1`).
55
+
56
+ `healthConnectVersion` is held at `1.1.0-beta01` on purpose: `1.1.0` stable
57
+ requires `compileSdk 36` and Android Gradle Plugin 8.9.1+, which React Native
58
+ did not ship until well after 0.76, so depending on it would fail
59
+ `checkDebugAarMetadata` in every app inside the `react-native >= 0.74` range
60
+ this package claims to support. `1.1.0-beta01` is the newest release that still
61
+ builds against `compileSdk 35` and exposes the same `Metadata` API. If your app
62
+ is already on `compileSdk 36` and AGP 8.9.1+, opt up with:
63
+
64
+ ```groovy
65
+ ext {
66
+ healthConnectVersion = "1.1.0"
67
+ }
68
+ ```
69
+
70
+
71
+ If Gradle warns that the Kotlin plugin is loaded twice with different versions,
72
+ that is this mismatch — set `kotlinVersion` and `kspVersion` as above.
73
+
74
+ ## 4. Manifest entries your app must add
75
+
76
+ The library manifest already merges in the service, the boot receiver, the
77
+ sensor permissions and the Health Connect permissions. Two things cannot be
78
+ merged and have to live in your app:
79
+
80
+ ### Health Connect rationale screen
81
+
82
+ Play rejects apps that request Health Connect permissions without a screen
83
+ explaining what the data is used for. Add both the intent filter (Android 13 and
84
+ below) and the alias (Android 14+) to whichever activity shows your privacy
85
+ policy:
86
+
87
+ ```xml
88
+ <activity android:name=".MainActivity" android:exported="true">
89
+ <intent-filter>
90
+ <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
91
+ </intent-filter>
92
+ </activity>
93
+
94
+ <activity-alias
95
+ android:name="ViewPermissionUsageActivity"
96
+ android:exported="true"
97
+ android:permission="android.permission.START_VIEW_PERMISSION_USAGE"
98
+ android:targetActivity=".MainActivity">
99
+ <intent-filter>
100
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
101
+ <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
102
+ </intent-filter>
103
+ </activity-alias>
104
+ ```
105
+
106
+ ### Trimming permissions you do not use
107
+
108
+ The library declares read and write for steps, distance and calories. Remove
109
+ any pair you will not use — Play asks you to justify every declared health
110
+ permission:
111
+
112
+ ```xml
113
+ <uses-permission
114
+ android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED"
115
+ tools:node="remove" />
116
+ ```
117
+
118
+ Same for `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` if you only ever call
119
+ `openBatteryOptimizationSettings()`:
120
+
121
+ ```xml
122
+ <uses-permission
123
+ android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
124
+ tools:node="remove" />
125
+ ```
126
+
127
+ ## 5. Custom notification icon (recommended)
128
+
129
+ The bundled icon is a generic footprint. Drop a white-on-transparent
130
+ `ic_stat_steps.xml` into `android/app/src/main/res/drawable/` and pass the name:
131
+
132
+ ```ts
133
+ await StepTracker.initialize({ notificationIcon: 'ic_stat_steps' });
134
+ ```
135
+
136
+ ## 6. Build
137
+
138
+ ```sh
139
+ cd android && ./gradlew clean && cd ..
140
+ npx react-native run-android
141
+ ```
142
+
143
+ The step counter does not exist on most emulators. Use a physical device, or
144
+ inject values on an emulator with:
145
+
146
+ ```sh
147
+ adb shell run-as <applicationId> \
148
+ sed -i 's/steps_today" value="[0-9]*"/steps_today" value="5000"/' \
149
+ shared_prefs/StepTrackerProState.xml
150
+ ```
151
+
152
+ ## 7. Minimum working setup
153
+
154
+ ```ts
155
+ import { useEffect } from 'react';
156
+ import StepTracker from 'react-native-step-tracker-pro';
157
+
158
+ useEffect(() => {
159
+ (async () => {
160
+ await StepTracker.initialize({ height: 175, weight: 75, dailyGoal: 10000 });
161
+ const perms = await StepTracker.requestPermissions();
162
+ if (perms.allGranted) await StepTracker.startTracking();
163
+ })();
164
+
165
+ const sub = StepTracker.addListener('stepsChanged', ({ steps }) => {
166
+ console.log(steps);
167
+ });
168
+ return () => sub.remove();
169
+ }, []);
170
+ ```
171
+
172
+ ## ProGuard / R8
173
+
174
+ `consumer-rules.pro` ships with the library and is applied automatically. No
175
+ app-side rules are needed.
@@ -0,0 +1,122 @@
1
+ # Play Store compliance
2
+
3
+ Read this before your first upload. A step tracker touches three of Play's
4
+ sensitive areas at once — foreground services, health data and battery
5
+ exemptions — and each has its own declaration form.
6
+
7
+ ## 1. Foreground service type
8
+
9
+ Play requires a declared type and a justification for every foreground service.
10
+ This package uses `health`.
11
+
12
+ **Play Console → App content → Foreground service permissions.**
13
+
14
+ - **Permission:** `FOREGROUND_SERVICE_HEALTH`
15
+ - **Purpose:** continuous step counting for a fitness feature
16
+ - **Why a foreground service is required:** the app must keep reading the step
17
+ sensor while the screen is off and the app is not in the foreground; deferred
18
+ work such as WorkManager cannot sample a sensor continuously
19
+ - **Video:** record a screen capture showing the notification appearing when
20
+ tracking starts, and the count updating while walking with the app closed.
21
+ Reviewers reject text-only submissions here.
22
+
23
+ The notification must be visible and honest. Do not hide it, do not make it
24
+ dismissible, and do not use a title that misrepresents what is running.
25
+
26
+ ## 2. Health Connect
27
+
28
+ **Play Console → Health apps declaration form.** Every permission declared in
29
+ the merged manifest has to be justified, so remove the ones you do not use
30
+ (see [INSTALLATION.md](INSTALLATION.md#trimming-permissions-you-do-not-use)).
31
+
32
+ Requirements:
33
+
34
+ - A privacy policy that specifically names the Health Connect data types you
35
+ read and write, states that data is stored on-device, and explains any
36
+ transmission off-device. Link it from both the Play listing and in-app.
37
+ - The `ACTION_SHOW_PERMISSIONS_RATIONALE` intent filter and the
38
+ `VIEW_PERMISSION_USAGE` activity alias. Missing these is the most common
39
+ rejection.
40
+ - No advertising, no selling health data, and no sharing it with third parties
41
+ for anything unrelated to the feature the user asked for.
42
+ - Data deletion must be possible from inside your app. `clearHistory()` covers
43
+ your local store; Health Connect data is deleted from the Health Connect app.
44
+
45
+ ### On duplicate step data
46
+
47
+ From Android 14 with SDK Extension 20 or higher, Health Connect records
48
+ on-device steps by itself once any app holds `READ_STEPS`. If you also write
49
+ steps, users can see the same walk twice in the Health Connect UI. Two sane
50
+ options:
51
+
52
+ - **Read-only** — leave `healthConnectEnabled: true` but never call the write
53
+ path. Use Health Connect as a source, your Room database as the record.
54
+ - **Write with attribution** — keep writing. Records carry a stable
55
+ `clientRecordId` so re-syncing does not stack duplicates, but they still sit
56
+ alongside the platform's own entries.
57
+
58
+ Pick one deliberately and say which in your privacy policy. Also note that as of
59
+ the June 2026 Health Connect update, on-device steps are attributed to a
60
+ device-specific synthetic package name rather than the generic `android` package,
61
+ which changes how you filter by source.
62
+
63
+ ## 3. Battery optimisation exemption
64
+
65
+ `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` is a restricted permission. Play allows
66
+ it only for a short list of use cases, and "our fitness app works better" is not
67
+ one of them. An app rejected here usually has to strip the permission and
68
+ resubmit.
69
+
70
+ Recommendation: **remove the permission** and use
71
+ `openBatteryOptimizationSettings()`, which opens the system list and lets the
72
+ user grant the exemption themselves. That path needs no declaration.
73
+
74
+ ```xml
75
+ <uses-permission
76
+ android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
77
+ tools:node="remove" />
78
+ ```
79
+
80
+ If you keep it, submit the **Permissions declaration form** and expect to argue
81
+ the case.
82
+
83
+ ## 4. ACTIVITY_RECOGNITION
84
+
85
+ A runtime permission from Android 10. Request it in context — after the user has
86
+ tapped something that clearly starts tracking — not on first launch. Play's data
87
+ safety form must list it under "Health and fitness → Fitness info".
88
+
89
+ ## 5. Data safety form
90
+
91
+ Declare, at minimum:
92
+
93
+ | Data type | Collected | Shared | Purpose |
94
+ |---|---|---|---|
95
+ | Fitness info (steps, distance, calories) | Yes | Only if you set `remoteSyncUrl` | App functionality |
96
+
97
+ If `remoteSyncUrl` is set, you are transmitting health data off-device: declare
98
+ it as shared, state the recipient, confirm encryption in transit (use HTTPS —
99
+ the worker will happily post to `http://`, and you should not), and offer a
100
+ deletion path.
101
+
102
+ ## 6. Notification content
103
+
104
+ Android 13+ makes notifications a runtime permission. Denying it hides the
105
+ notification but does not stop the service, and `allGranted` in
106
+ `checkPermissions()` reflects that deliberately. Do not block your onboarding on
107
+ `POST_NOTIFICATIONS`.
108
+
109
+ ## 7. Pre-launch checklist
110
+
111
+ - [ ] Foreground service declaration submitted, with a video
112
+ - [ ] Health apps declaration submitted, only for permissions you actually use
113
+ - [ ] Privacy policy names each Health Connect data type
114
+ - [ ] `ACTION_SHOW_PERMISSIONS_RATIONALE` filter and `VIEW_PERMISSION_USAGE`
115
+ alias present in the app manifest
116
+ - [ ] `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` removed, or declared and justified
117
+ - [ ] Unused `android.permission.health.*` entries removed with `tools:node="remove"`
118
+ - [ ] Data safety form matches whether `remoteSyncUrl` is configured
119
+ - [ ] `ACTIVITY_RECOGNITION` requested in context, with an explanation on screen
120
+ - [ ] In-app data deletion available
121
+ - [ ] Tested on a Xiaomi or Oppo device, where autostart restrictions kill
122
+ services that survive fine on Pixel
@@ -0,0 +1,312 @@
1
+ # Testing the package
2
+
3
+ Two layers. The engine logic — reboot, midnight, pause, counter resets — is
4
+ covered by instrumented tests that feed samples directly, so no walking is
5
+ needed. Everything that involves the OS keeping your process alive has to be
6
+ verified on a real device.
7
+
8
+ ---
9
+
10
+ ## 1. Link the package into a test app
11
+
12
+ It is not published yet, so point an app at the folder.
13
+
14
+ ```sh
15
+ # in the package
16
+ npm pack # → react-native-step-tracker-pro-1.0.0.tgz
17
+
18
+ # in your test app
19
+ npm install ../react-native-step-tracker-pro/react-native-step-tracker-pro-1.0.0.tgz
20
+ ```
21
+
22
+ `npm pack` is better than `file:` or `yarn link` here: Metro follows symlinks
23
+ badly, and `file:` installs a symlink on npm 7+. Re-run `npm pack` + install
24
+ after every change to the package.
25
+
26
+ Then set the SDK levels in the app's `android/build.gradle`:
27
+
28
+ ```gradle
29
+ buildscript {
30
+ ext {
31
+ minSdkVersion = 26
32
+ compileSdkVersion = 35
33
+ targetSdkVersion = 35
34
+ }
35
+ }
36
+ ```
37
+
38
+ ```sh
39
+ cd android && ./gradlew clean && cd ..
40
+ npx react-native run-android
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 2. Run the engine tests
46
+
47
+ ```sh
48
+ cd android
49
+ ./gradlew :react-native-step-tracker-pro:connectedAndroidTest
50
+ ```
51
+
52
+ Runs fine on an emulator — the tests never touch the sensor. Eleven cases
53
+ covering:
54
+
55
+ | Test | What breaks if it fails |
56
+ |---|---|
57
+ | `accumulatesCounterDeltas` | basic counting |
58
+ | `sensorResetWithoutRebootDoesNotDoubleCount` | OEM HAL restarts inflate the total |
59
+ | `rebootOnSameDayClaimsStepsTakenSinceBoot` | steps between boot and service start are lost |
60
+ | `rebootOnPreviousDayDropsPreServiceSteps` | yesterday's steps land in today |
61
+ | `midnightRolloverFinalisesPreviousDay` | day totals never close, or leak forward |
62
+ | `pausedStepsAreDiscardedAndResumeDoesNotBackfill` | pause does nothing, or resume dumps a backlog |
63
+ | `detectorFallbackIncrementsDirectly` | no-step-counter devices count nothing |
64
+ | `commitThresholdControlsDatabaseWrites` | a write per step, or no writes at all |
65
+ | `resetTodayClearsTotalButKeepsCountingAfterwards` | QA reset replays the old count |
66
+ | `metricsDeriveStrideFromHeightWhenNotOverridden` | distance/calories maths |
67
+ | `calendarWeekIsMondayAnchoredAndSevenDaysLong` | weekly stats window |
68
+
69
+ Run one case:
70
+
71
+ ```sh
72
+ ./gradlew :react-native-step-tracker-pro:connectedAndroidTest \
73
+ -Pandroid.testInstrumentationRunnerArguments.class=com.steptrackerpro.StepCounterEngineTest#rebootOnSameDayClaimsStepsTakenSinceBoot
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 3. Device check before anything else
79
+
80
+ Most emulators have no step hardware, so live counting can only be tested on a
81
+ phone.
82
+
83
+ ```sh
84
+ adb shell dumpsys sensorservice | grep -i step
85
+ ```
86
+
87
+ Or from JS:
88
+
89
+ ```ts
90
+ console.log(await StepTracker.getDeviceCapabilities());
91
+ // { hasStepCounter: true, hasStepDetector: true, supported: true, ... }
92
+ ```
93
+
94
+ If `supported` is false, everything below is untestable on that device.
95
+
96
+ ---
97
+
98
+ ## 4. Manual matrix
99
+
100
+ Set `PKG` to your app's applicationId first: `PKG=com.yourapp`
101
+
102
+ ### App open
103
+
104
+ Start tracking, walk 20–30 steps. Sensor hubs often buffer the first few, so
105
+ walk properly rather than shaking the phone.
106
+
107
+ - [ ] `stepsChanged` fires in JS
108
+ - [ ] notification shows steps, km, kcal and the progress bar
109
+ - [ ] numbers in the notification and the UI agree
110
+
111
+ ### Background and screen locked
112
+
113
+ ```sh
114
+ adb shell input keyevent KEYCODE_HOME
115
+ adb shell input keyevent KEYCODE_POWER # screen off
116
+ # walk 50 steps
117
+ adb shell input keyevent KEYCODE_POWER
118
+ ```
119
+
120
+ - [ ] lock screen notification shows an updated count
121
+ - [ ] reopening the app shows the same total, not a stale one
122
+
123
+ ### App swiped away
124
+
125
+ Swipe the app out of recents.
126
+
127
+ - [ ] notification stays
128
+ - [ ] count still rises while walking
129
+ - [ ] `adb shell dumpsys activity services $PKG | grep StepTrackerService` shows it running
130
+
131
+ ### Process killed
132
+
133
+ ```sh
134
+ adb shell am force-stop $PKG
135
+ # walk 100 steps with the app dead
136
+ adb shell monkey -p $PKG -c android.intent.category.LAUNCHER 1
137
+ ```
138
+
139
+ - [ ] the 100 steps appear after relaunch
140
+
141
+ This is the single most valuable test. `TYPE_STEP_COUNTER` kept counting in the
142
+ sensor hub while your process was gone; the first sample after restart should
143
+ reconcile them in. If they are missing, the anchor logic is wrong.
144
+
145
+ ### Boot recovery
146
+
147
+ Real reboot:
148
+
149
+ ```sh
150
+ adb reboot
151
+ # wait for the lock screen, do not open the app
152
+ ```
153
+
154
+ - [ ] notification reappears on its own within a few seconds of unlock
155
+ - [ ] yesterday's / today's total is intact
156
+
157
+ Faster loop while iterating — fires the receiver without rebooting:
158
+
159
+ ```sh
160
+ adb shell am broadcast -a android.intent.action.BOOT_COMPLETED \
161
+ -n $PKG/com.steptrackerpro.service.BootReceiver
162
+ ```
163
+
164
+ This exercises the receiver's guard rails (initialised, `autoStartOnBoot`,
165
+ `shouldAutoStart`, permissions) but not the counter reset — only a real reboot
166
+ zeroes the hardware counter.
167
+
168
+ ### Midnight rollover
169
+
170
+ No root needed. Settings → System → Date & time → turn off automatic, set the
171
+ date forward one day. Then take a few steps.
172
+
173
+ - [ ] `dayChanged` fires with yesterday's total
174
+ - [ ] today starts at 0, not at yesterday's number
175
+ - [ ] yesterday's row is in the database with its final count
176
+
177
+ Set the date back afterwards. On an emulator you can do it from the shell:
178
+
179
+ ```sh
180
+ adb root && adb shell date $(date -v+1d +%m%d%H%M%Y.%S) # macOS date syntax
181
+ adb shell am broadcast -a android.intent.action.TIME_SET
182
+ ```
183
+
184
+ ### Doze
185
+
186
+ ```sh
187
+ adb shell dumpsys battery unplug
188
+ adb shell dumpsys deviceidle force-idle
189
+ # walk
190
+ adb shell dumpsys deviceidle unforce && adb shell dumpsys battery reset
191
+ ```
192
+
193
+ - [ ] steps taken during forced idle are present afterwards
194
+
195
+ ### OEM battery management
196
+
197
+ Test on a Xiaomi, Oppo, Vivo or Realme device if you have one. They kill
198
+ foreground services that survive fine on a Pixel.
199
+
200
+ - [ ] with autostart off: counting stops (expected)
201
+ - [ ] `openManufacturerAutoStartSettings()` lands on the right screen
202
+ - [ ] with autostart on: counting survives an hour with the screen off
203
+
204
+ ### Health Connect
205
+
206
+ Android 14+ has it built in. Android 13 and below need the app from Play.
207
+
208
+ ```sh
209
+ adb shell am start -a android.health.connect.action.HEALTH_HOME_SETTINGS
210
+ ```
211
+
212
+ - [ ] `getHealthConnectStatus()` reports `available: true`
213
+ - [ ] `requestHealthConnectPermissions()` opens the sheet (needs the rationale
214
+ intent filter in your app manifest, or it silently does nothing)
215
+ - [ ] after `syncNow()`, today's steps appear in the Health Connect app
216
+ - [ ] syncing twice does **not** create two entries for the same day
217
+ - [ ] `readHealthConnectSteps()` returns data written by other apps too
218
+
219
+ ### Offline
220
+
221
+ ```sh
222
+ adb shell svc wifi disable && adb shell svc data disable
223
+ ```
224
+
225
+ - [ ] counting, notification, stats and history all work unchanged
226
+ - [ ] `getPendingSyncCount()` rises if `remoteSyncUrl` is set
227
+ - [ ] after re-enabling the network, `syncCompleted` fires for `target: 'remote'`
228
+ within the hour, and pending drops to 0
229
+
230
+ ### Goals
231
+
232
+ Set `dailyGoal: 20` temporarily.
233
+
234
+ - [ ] `goalProgressChanged` fires as the percentage climbs
235
+ - [ ] `goalReached` fires once, not on every step after the goal
236
+ - [ ] killing and relaunching the app does not re-fire it for the same day
237
+
238
+ ### Retention
239
+
240
+ Set `historyRetentionDays: 2`, then `pruneHistory()`.
241
+
242
+ - [ ] rows older than 2 days are gone
243
+ - [ ] `getMonthlyStats()` still returns a zero-filled range without crashing
244
+
245
+ ---
246
+
247
+ ## 5. Inspection
248
+
249
+ ```sh
250
+ # counter state — the file to look at when a number seems wrong
251
+ adb shell run-as $PKG cat shared_prefs/StepTrackerProState.xml
252
+
253
+ # config
254
+ adb shell run-as $PKG cat shared_prefs/StepTrackerProConfig.xml
255
+
256
+ # service alive?
257
+ adb shell dumpsys activity services $PKG
258
+
259
+ # scheduled sync work
260
+ adb shell dumpsys jobscheduler | grep -A 8 $PKG
261
+
262
+ # logs
263
+ adb logcat -s StepTrackerService:V StepTrackerBoot:V
264
+ ```
265
+
266
+ For the Room tables use Android Studio → **View → Tool Windows → App
267
+ Inspection → Database Inspector**. It reads a live database, which is far less
268
+ painful than pulling the file.
269
+
270
+ Reading `StepTrackerProState.xml`:
271
+
272
+ | Key | Meaning |
273
+ |---|---|
274
+ | `steps_today` | current total |
275
+ | `anchor_steps` / `anchor_value` | the pin — `steps_today = anchor_steps + (raw − anchor_value)` |
276
+ | `last_raw_value` | last cumulative reading from the sensor |
277
+ | `boot_id` | approximate boot epoch; a jump here means a reboot was detected |
278
+ | `active_date` | the day the total belongs to |
279
+ | `should_auto_start` | whether the boot receiver will restart the service |
280
+
281
+ ### Injecting a fake count for UI work
282
+
283
+ Only for screenshots — set `anchor_steps` too, or the next sensor sample
284
+ recomputes the total and wipes your edit.
285
+
286
+ ```sh
287
+ adb shell am force-stop $PKG
288
+ adb shell run-as $PKG sh -c \
289
+ "sed -i -e 's/\"steps_today\" value=\"[0-9]*\"/\"steps_today\" value=\"7500\"/' \
290
+ -e 's/\"anchor_steps\" value=\"[0-9]*\"/\"anchor_steps\" value=\"7500\"/' \
291
+ shared_prefs/StepTrackerProState.xml"
292
+ adb shell monkey -p $PKG -c android.intent.category.LAUNCHER 1
293
+ ```
294
+
295
+ The force-stop matters: SharedPreferences caches in memory, so an edit made
296
+ while the process is alive gets overwritten on the next write.
297
+
298
+ ---
299
+
300
+ ## 6. Order to test in
301
+
302
+ 1. Instrumented tests on an emulator — catches logic regressions in a minute
303
+ 2. Physical device: capabilities → permissions → live counting
304
+ 3. Background, lock, swipe away
305
+ 4. Force-stop recovery
306
+ 5. Real reboot
307
+ 6. Date rollover
308
+ 7. Health Connect
309
+ 8. OEM device, if you have one
310
+
311
+ Steps 1–4 catch almost everything. Steps 5–8 are where devices disagree with
312
+ each other, so they need real hardware rather than an emulator.
@@ -0,0 +1,96 @@
1
+ # Troubleshooting
2
+
3
+ ## Steps stay at zero
4
+
5
+ 1. `getDeviceCapabilities()` — if `supported` is false the device has no step
6
+ hardware. Most emulators do not.
7
+ 2. `checkPermissions()` — `ACTIVITY_RECOGNITION` must be granted.
8
+ 3. `getTrackingState()` — `idle` or `stopped` means the service was never
9
+ started or was stopped.
10
+ 4. Some sensor hubs need 5–10 actual steps before the first event fires.
11
+ Shaking the phone does nothing; walk.
12
+
13
+ ## Counting stops when the screen goes off
14
+
15
+ Almost always OEM battery management, not Doze.
16
+
17
+ ```ts
18
+ if (await StepTracker.isBatteryOptimizationEnabled()) {
19
+ await StepTracker.openBatteryOptimizationSettings();
20
+ }
21
+ await StepTracker.openManufacturerAutoStartSettings(); // Xiaomi, Oppo, Vivo, Huawei
22
+ ```
23
+
24
+ On Xiaomi, "Autostart" and "No restrictions" under battery saver both have to be
25
+ set. On Samsung, remove the app from "Sleeping apps". There is no API for either;
26
+ you have to walk the user there.
27
+
28
+ ## Notification does not appear
29
+
30
+ - Android 13+: `POST_NOTIFICATIONS` denied. Counting continues, the notification
31
+ does not show.
32
+ - The channel is created at `IMPORTANCE_LOW`; if the user turned the channel off
33
+ it stays off. Send them to app notification settings.
34
+
35
+ ## Steps jumped after a reboot
36
+
37
+ Expected on the boundary. Steps taken between boot and the service restarting
38
+ are counted if the boot happened on the same day, and dropped if it happened
39
+ before midnight. See [ARCHITECTURE.md](ARCHITECTURE.md#reboot).
40
+
41
+ ## Steps reset when I reinstalled
42
+
43
+ SharedPreferences and the Room database are cleared on uninstall. If you need
44
+ history to survive reinstalls, sync to Health Connect or set `remoteSyncUrl`.
45
+
46
+ ## Build fails: KSP version mismatch
47
+
48
+ Your app is on Kotlin 2.x and the library defaults to 1.9.24. Set `kotlinVersion`
49
+ and a matching `kspVersion` in the app's root `build.gradle` — see
50
+ [INSTALLATION.md](INSTALLATION.md#3-kotlin-2x-only-if-your-app-is-already-on-it).
51
+
52
+ ## Build fails: `minSdkVersion 24 cannot be smaller than 26`
53
+
54
+ Set `minSdkVersion = 26` in the app's root `build.gradle`. Health Connect and
55
+ `java.time` both need it.
56
+
57
+ ## `ForegroundServiceDidNotStartInTimeException`
58
+
59
+ Something in your app is delaying the service start past the 5-second window,
60
+ usually a heavy `Application.onCreate`. The service calls `startForeground`
61
+ first thing, so the delay is upstream.
62
+
63
+ ## Health Connect permission sheet never opens
64
+
65
+ - `getHealthConnectStatus().available` is false → the provider is not installed
66
+ (Android 13 and below) or needs an update (`requiresUpdate`).
67
+ - The user permanently denied. The sheet will not reappear; use
68
+ `openHealthConnectSettings()`.
69
+ - Your app manifest is missing the rationale intent filter. Health Connect
70
+ refuses to show the sheet without it.
71
+
72
+ ## Events fire in dev but not after a JS reload
73
+
74
+ `invalidate()` unsubscribes the module from the event bus on teardown. If you
75
+ hold a listener across a reload, re-register in a `useEffect` — the bundled
76
+ hooks already do.
77
+
78
+ ## Numbers do not match Google Fit or Samsung Health
79
+
80
+ They will not, exactly. Those apps fuse the step counter with accelerometer and
81
+ GPS data and apply their own filtering; neither publishes its model. Steps
82
+ should land within a few percent. Distance and calories will differ more,
83
+ because both are estimates from height and weight here. Tune `strideLength` and
84
+ `calorieCoefficient` if you have better data.
85
+
86
+ ## Inspecting state during QA
87
+
88
+ ```sh
89
+ adb shell run-as <applicationId> cat shared_prefs/StepTrackerProState.xml
90
+ adb shell dumpsys activity services com.steptrackerpro.service.StepTrackerService
91
+ adb logcat -s StepTrackerService StepTrackerBoot
92
+ ```
93
+
94
+ Simulating a reboot without rebooting: force-stop the app, then start it again.
95
+ The boot ID is unchanged, so the counter reconciles rather than re-anchoring —
96
+ which is exactly the process-death path you want to test.