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.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/android/build.gradle +119 -0
- package/android/consumer-rules.pro +13 -0
- package/android/gradle.properties +3 -0
- package/android/src/main/AndroidManifest.xml +78 -0
- package/android/src/main/java/com/steptrackerpro/StepTrackerProModule.kt +598 -0
- package/android/src/main/java/com/steptrackerpro/StepTrackerProPackage.kt +26 -0
- package/android/src/main/java/com/steptrackerpro/core/DateKeys.kt +90 -0
- package/android/src/main/java/com/steptrackerpro/core/GoalTracker.kt +66 -0
- package/android/src/main/java/com/steptrackerpro/core/MetricsCalculator.kt +48 -0
- package/android/src/main/java/com/steptrackerpro/core/Models.kt +73 -0
- package/android/src/main/java/com/steptrackerpro/core/StepCounterEngine.kt +338 -0
- package/android/src/main/java/com/steptrackerpro/core/StepStateStore.kt +137 -0
- package/android/src/main/java/com/steptrackerpro/core/StepTrackerConfig.kt +184 -0
- package/android/src/main/java/com/steptrackerpro/core/StepTrackerCore.kt +280 -0
- package/android/src/main/java/com/steptrackerpro/db/Entities.kt +67 -0
- package/android/src/main/java/com/steptrackerpro/db/StepDao.kt +118 -0
- package/android/src/main/java/com/steptrackerpro/db/StepDatabase.kt +60 -0
- package/android/src/main/java/com/steptrackerpro/db/StepRepository.kt +146 -0
- package/android/src/main/java/com/steptrackerpro/health/HealthConnectManager.kt +198 -0
- package/android/src/main/java/com/steptrackerpro/health/HealthPermissionActivity.kt +99 -0
- package/android/src/main/java/com/steptrackerpro/service/BootReceiver.kt +54 -0
- package/android/src/main/java/com/steptrackerpro/service/NotificationActionReceiver.kt +17 -0
- package/android/src/main/java/com/steptrackerpro/service/NotificationFactory.kt +170 -0
- package/android/src/main/java/com/steptrackerpro/service/ServiceCommands.kt +42 -0
- package/android/src/main/java/com/steptrackerpro/service/StepTrackerService.kt +409 -0
- package/android/src/main/java/com/steptrackerpro/sync/SyncScheduler.kt +102 -0
- package/android/src/main/java/com/steptrackerpro/sync/SyncWorkers.kt +175 -0
- package/android/src/main/java/com/steptrackerpro/util/BatteryOptimizationHelper.kt +94 -0
- package/android/src/main/java/com/steptrackerpro/util/Bridge.kt +149 -0
- package/android/src/main/java/com/steptrackerpro/util/PermissionHelper.kt +92 -0
- package/android/src/main/java/com/steptrackerpro/util/StepEventBus.kt +53 -0
- package/android/src/main/res/drawable/stp_ic_steps.xml +9 -0
- package/android/src/main/res/values/strings.xml +9 -0
- package/android/src/main/res/values/styles.xml +8 -0
- package/android/src/newarch/java/com/steptrackerpro/StepTrackerProSpec.kt +10 -0
- package/android/src/oldarch/java/com/steptrackerpro/StepTrackerProSpec.kt +60 -0
- package/docs/API.md +329 -0
- package/docs/ARCHITECTURE.md +203 -0
- package/docs/INSTALLATION.md +175 -0
- package/docs/PLAY_STORE_COMPLIANCE.md +122 -0
- package/docs/TESTING.md +312 -0
- package/docs/TROUBLESHOOTING.md +96 -0
- package/lib/commonjs/NativeStepTrackerPro.js +14 -0
- package/lib/commonjs/NativeStepTrackerPro.js.map +1 -0
- package/lib/commonjs/StepTracker.js +290 -0
- package/lib/commonjs/StepTracker.js.map +1 -0
- package/lib/commonjs/constants.js +30 -0
- package/lib/commonjs/constants.js.map +1 -0
- package/lib/commonjs/errors.js +24 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/hooks/index.js +20 -0
- package/lib/commonjs/hooks/index.js.map +1 -0
- package/lib/commonjs/hooks/useStepStats.js +51 -0
- package/lib/commonjs/hooks/useStepStats.js.map +1 -0
- package/lib/commonjs/hooks/useStepTracker.js +134 -0
- package/lib/commonjs/hooks/useStepTracker.js.map +1 -0
- package/lib/commonjs/index.js +64 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/NativeStepTrackerPro.js +12 -0
- package/lib/module/NativeStepTrackerPro.js.map +1 -0
- package/lib/module/StepTracker.js +284 -0
- package/lib/module/StepTracker.js.map +1 -0
- package/lib/module/constants.js +26 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/errors.js +18 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useStepStats.js +46 -0
- package/lib/module/hooks/useStepStats.js.map +1 -0
- package/lib/module/hooks/useStepTracker.js +129 -0
- package/lib/module/hooks/useStepTracker.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/NativeStepTrackerPro.d.ts +50 -0
- package/lib/typescript/NativeStepTrackerPro.d.ts.map +1 -0
- package/lib/typescript/StepTracker.d.ts +76 -0
- package/lib/typescript/StepTracker.d.ts.map +1 -0
- package/lib/typescript/constants.d.ts +10 -0
- package/lib/typescript/constants.d.ts.map +1 -0
- package/lib/typescript/errors.d.ts +8 -0
- package/lib/typescript/errors.d.ts.map +1 -0
- package/lib/typescript/hooks/index.d.ts +5 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -0
- package/lib/typescript/hooks/useStepStats.d.ts +11 -0
- package/lib/typescript/hooks/useStepStats.d.ts.map +1 -0
- package/lib/typescript/hooks/useStepTracker.d.ts +26 -0
- package/lib/typescript/hooks/useStepTracker.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +7 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +183 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +79 -0
- package/src/NativeStepTrackerPro.ts +66 -0
- package/src/StepTracker.ts +410 -0
- package/src/constants.ts +43 -0
- package/src/errors.ts +31 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useStepStats.ts +55 -0
- package/src/hooks/useStepTracker.ts +167 -0
- package/src/index.ts +27 -0
- 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
|
package/docs/TESTING.md
ADDED
|
@@ -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.
|