Skip to content

Commit 6bc6c6d

Browse files
Improve docs (#57)
* Improve docs * add support for skipIfWhitelistOrBlacklistIsUnchanged * chore: update version * fix tests * remove extra comma * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: update version * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 1280393 commit 6bc6c6d

File tree

6 files changed

+232
-10
lines changed

6 files changed

+232
-10
lines changed

README.md

Lines changed: 147 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,35 @@ Provides direct access to Apples Screen Time, Device Activity and Shielding APIs
1111

1212
Please note that it only supports iOS (and requires iOS 15 or higher) and requires a Custom Dev Client to work with Expo. For Android I'd probably look into [UsageStats](https://developer.android.com/reference/android/app/usage/UsageStats), which seems provide more granularity.
1313

14-
# Examples
14+
# Examples & Use Cases
15+
16+
## Handle permissions
17+
18+
To block apps, you need to request Screen Time permissions. Note that some features (for example, events) may still trigger without permissions; however, this behavior is not guaranteed.
1519

1620
```TypeScript
21+
import React, { useEffect } from 'react';
1722
import * as ReactNativeDeviceActivity from "react-native-device-activity";
23+
import React, { useEffect } from 'react';
24+
25+
useEffect(() => {
26+
ReactNativeDeviceActivity.requestAuthorization();
27+
}, [])
28+
29+
You can also revoke permissions:
30+
31+
```TypeScript
32+
import * as ReactNativeDeviceActivity from "react-native-device-activity";
33+
34+
ReactNativeDeviceActivity.revokeAuthorization();
35+
36+
## Select Apps to track
37+
38+
For most use cases you need to get an activitySelection from the user, which is a token representing the apps the user wants to track, block or whitelist. This can be done by presenting the native view:
39+
40+
```TypeScript
41+
import * as ReactNativeDeviceActivity from "react-native-device-activity";
42+
1843

1944
const DeviceActivityPicker = () => {
2045
// First things first, you need to request authorization
@@ -36,11 +61,24 @@ const DeviceActivityPicker = () => {
3661
)
3762
}
3863
}
64+
```
65+
66+
Some things worth noting here:
67+
68+
- This is a SwiftUI view, which is prone to crashing, especially when browsing larger categories of apps or searching for apps. It's recommended to provide a fallback view (positioned behind the SwiftUI view) that allows the user to know what's happening and reload the view and tailor that to your app's design and UX.
69+
The activitySelection tokens can be particularly large (especially if you use includeEntireCategory flag), so you probably want to reference them through a familyActivitySelectionId instead of always passing the string token around. Most functions in this library accept a familyActivitySelectionId as well as the familyActivitySelection token directly.
70+
71+
## Time tracking
72+
73+
It's worth noting that the Screen Time API is not designed for time tracking out-of-the-box. So you have to set up events with names you can parse as time after they've triggered.
74+
75+
```TypeScript
76+
import * as ReactNativeDeviceActivity from "react-native-device-activity";
3977

4078
// once you have authorization and got hold of the familyActivitySelection (which is a base64 string) you can start tracking with it:
4179
const trackDeviceActivity = (activitySelection: string) => {
4280
ReactNativeDeviceActivity.startMonitoring(
43-
"DeviceActivity.AppLoggedTimeDaily",
81+
"TimeTrackingActivity",
4482
{
4583
// repeat logging every 24 hours
4684
intervalStart: { hour: 0, minute: 0, second: 0 },
@@ -49,15 +87,15 @@ const trackDeviceActivity = (activitySelection: string) => {
4987
},
5088
events: [
5189
{
52-
eventName: 'user_activity_reached_10_minutes',
90+
eventName: 'minutes_reached_10', // remember to give event names that make it possible for you to extract time at a later stage, if you want to access this information
5391
familyActivitySelection: activitySelection,
5492
threshold: { minute: 10 },
5593
}
5694
]
5795
);
5896
}
5997

60-
// you can listen to events (which I guess only works when the app is alive):
98+
// you can listen to events (which only works when the app is alive):
6199
const listener = ReactNativeDeviceActivity.onDeviceActivityMonitorEvent(
62100
(event) => {
63101
const name = event.nativeEvent.callbackName; // the name of the event
@@ -76,6 +114,102 @@ const listener = ReactNativeDeviceActivity.onDeviceActivityMonitorEvent(
76114
const events = ReactNativeDeviceActivityModule.getEvents();
77115
```
78116

117+
Some things worth noting here:
118+
119+
Depending on your use case (if you need different schedules for different days, for example) you might need multiple monitors. There's a hard limit on 20 monitors at the same time. Study the [DateComponents](https://developer.apple.com/documentation/foundation/datecomponents) object to model this to your use case.
120+
121+
## Block the shield
122+
123+
To block apps, you can do it directly from your code.
124+
125+
```TypeScript
126+
import * as ReactNativeDeviceActivity from "react-native-device-activity";
127+
128+
// block all apps
129+
ReactNativeDeviceActivity.blockSelection({
130+
activitySelectionId: selectionId,
131+
});
132+
```
133+
134+
But for many use cases you want to do this in the Swift process, which is why you can specify actions when setting up events:
135+
136+
```TypeScript
137+
const trackDeviceActivity = (activitySelection: string) => {
138+
ReactNativeDeviceActivity.startMonitoring(
139+
"BlockAfter10Minutes",
140+
{
141+
// repeat logging every 24 hours
142+
intervalStart: { hour: 0, minute: 0, second: 0 },
143+
intervalEnd: { hour: 23, minute: 59, second: 59 },
144+
repeats: true,
145+
},
146+
events: [
147+
{
148+
eventName: 'minutes_reached_10', // remember to give event names that make it possible for you to extract time at a later stage, if you want to access this information
149+
familyActivitySelection: activitySelection,
150+
threshold: { minute: 10 },
151+
actions: [
152+
{
153+
type: "blockSelection",
154+
familyActivitySelectionId,
155+
}
156+
]
157+
}
158+
]
159+
);
160+
}
161+
```
162+
163+
There are many other actions you can perform, like sending web requests or notifications. The easiest way to explore this is by using TypeScript, which is easier to keep up-to-date than this documentation.
164+
165+
You can also configure the shield UI and actions of the shield (this can also be done in the Swift process with actions):
166+
167+
```TypeScript
168+
ReactNativeDeviceActivity.updateShield(
169+
{
170+
title: shieldTitle,
171+
backgroundBlurStyle: UIBlurEffectStyle.systemMaterialDark,
172+
// backgroundColor: null,
173+
titleColor: {
174+
red: 255,
175+
green: 0,
176+
blue: 0,
177+
},
178+
subtitle: "subtitle",
179+
subtitleColor: {
180+
red: Math.random() * 255,
181+
green: Math.random() * 255,
182+
blue: Math.random() * 255,
183+
},
184+
primaryButtonBackgroundColor: {
185+
red: Math.random() * 255,
186+
green: Math.random() * 255,
187+
blue: Math.random() * 255,
188+
},
189+
primaryButtonLabelColor: {
190+
red: Math.random() * 255,
191+
green: Math.random() * 255,
192+
blue: Math.random() * 255,
193+
},
194+
secondaryButtonLabelColor: {
195+
red: Math.random() * 255,
196+
green: Math.random() * 255,
197+
blue: Math.random() * 255,
198+
},
199+
},
200+
{
201+
primary: {
202+
type: "disableBlockAllMode",
203+
behavior: "defer",
204+
},
205+
secondary: {
206+
type: "dismiss",
207+
behavior: "close",
208+
},
209+
},
210+
)
211+
```
212+
79213
# Installation in managed Expo projects
80214

81215
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects &mdash; it is likely to be included in an upcoming Expo SDK release.
@@ -111,15 +245,17 @@ For Expo to be able to automatically handle provisioning you need to specify ext
111245
You can potentially modify the targets manually, although you risk the library and your app code diverging. If you want to disable the automatic copying of the targets, you can set `copyToTargetFolder` to `false` in the plugin configuration [as seen here](https://github.com/Intentional-Digital/react-native-device-activity/blob/main/example/app.json#L53).
112246

113247
## Some notes
114-
- It's not possible to 100% know which familyActivitySelection an event being handled is triggered for in the context of the Shield UI/actions. We try to make a best guess here - prioritizing apps/websites in an activitySelection over categories, and smaller activitySelections over larger ones (i.e. "Instagram" over "Instagram + Facebook" over "Social Media Apps"). This means that if you display a shield specific for the Instagram selection that will take precedence over the less specific shields.
248+
249+
- It's not possible to 100% know which familyActivitySelection an event being handled is triggered for in the context of the Shield UI/actions. We try to make the best guess here, prioritizing apps/websites in an activitySelection over categories, and smaller activitySelections over larger ones (i.e. "Instagram" over "Instagram + Facebook" over "Social Media Apps"). This means that if you display a shield specific for the Instagram selection that will take precedence over the less specific shields.
115250
- When determining which familyActivitySelectionId that should be used it will only look for familyActivitySelectionIds that are contained in any of the currently monitored activity names (i.e. if familyActivitySelectionId is "social-media-apps" it will only trigger if there is an activity name that contains "social-media-apps"). This might be a limitation for some implementations, it would probably be nice to make this configurable.
116251

117252
## Data model
253+
118254
Almost all the functionality is built around persisting configuration as well as event history to UserDefaults.
119255

120256
- familyActivitySelectionId mapping. This makes it possible for us to tie a familyActivitySelection token to an id that we can reuse and refer to at a later stage.
121-
- Triggers. This includes configuring shield UI/actions as well as sending web requests or notifications from the Swift background side, in the context of the device activity monitor process. Prefixed like actions_for_${goalId} in userDefaults. This is how we do blocking of apps, updates to shield UI/actions etc.
122-
- Event history. Contains information of which events have been triggered and when. Prefixed like events_${goalId} in userDefaults. This can be useful for tracking time spent.
257+
- Triggers. This includes configuring shield UI/actions as well as sending web requests or notifications from the Swift background side, in the context of the device activity monitor process. Prefixed like actions*for*${goalId} in userDefaults. This is how we do blocking of apps, updates to shield UI/actions etc.
258+
- Event history. Contains information of which events have been triggered and when. Prefixed like events\_${goalId} in userDefaults. This can be useful for tracking time spent.
123259
- ShieldIds. To reduce the storage strain on userDefaults shields are referenced with shieldIds.
124260

125261
# Installation in bare React Native projects
@@ -160,7 +296,8 @@ Contributions are very welcome! Please refer to guidelines described in the [con
160296
# Weird behaviors ⚠️
161297

162298
- Authorization changes outside app not captured
163-
When we've asked whether the user has authorized us to use screen time, and the state is changed outside the app, the native API doesn't update until the app restarts, i.e. this flow:
299+
When we've asked whether the user has authorized us to use screen time, and the state is changed outside the app, the native API doesn't update until the app restarts, i.e. this flow:
300+
164301
1. Ask for current permission
165302
2. Change permission outside the app
166303
3. Ask for current permission again will return same as (1)
@@ -173,6 +310,7 @@ When we've asked whether the user has authorized us to use screen time, and the
173310
- The DeviceActivitySelectionView is prone to crashes, which is outside of our control. The best we can do is provide fallback views that allows the user to know what's happening and reload the view.
174311

175312
# Troubleshooting 📱
313+
176314
The Screen Time APIs are known to be very finnicky. Here are some things you can try to troubleshoot events not being reported:
177315

178316
- Disable Low Power Mode (mentioned by Apple Community Specialist [here](https://discussions.apple.com/thread/254808070)) 🪫
@@ -182,4 +320,4 @@ The Screen Time APIs are known to be very finnicky. Here are some things you can
182320
- Make sure device is not low on storage (mentioned by Apple Community Specialist [here](https://discussions.apple.com/thread/254808070)) 💾
183321
- Upgrade iOS version
184322
- Content & Privacy Restrictions: If any restrictions are enabled under Screen Time’s Content & Privacy Restrictions, ensure none are blocking your app.
185-
- Reset all device settings
323+
- Reset all device settings

0 commit comments

Comments
 (0)