Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
React Native Device Activity
https://github.com/kingstinct/react-native-device-activity
Admin
A React Native wrapper for Apple's Screen Time, Device Activity, and Family Controls APIs, enabling
...
Tokens:
26,935
Snippets:
180
Trust Score:
7.8
Update:
5 days ago
Context
Skills
Chat
Benchmark
84
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# react-native-device-activity `react-native-device-activity` is a React Native library (v0.6.1) that provides a TypeScript/JavaScript wrapper around Apple's Screen Time, Device Activity, and Family Controls APIs on iOS 15+. It enables developers to monitor app usage, block applications on schedules, customize the blocking shield UI, set web content filter policies, and react to device activity events — all from React Native code. The library is built with Expo Modules Core and requires special entitlements from Apple (Family Controls distribution) to be used in production builds. The library is organized around four core Apple APIs: **FamilyControls** (authorization and app selection), **DeviceActivity** (scheduling monitors and threshold events), **ManagedSettings** (applying blocks, whitelists, and web filters), and **ManagedSettingsUI** (configuring the shield appearance shown to users when content is blocked). All persistent state is stored in a shared `UserDefaults` App Group so native extension processes (ActivityMonitor, ShieldAction, ShieldConfiguration) can read and act on configurations even when the main app is in the background or killed. --- ## Installation Install the package and configure the Expo plugin with your Apple Team ID and App Group. ```bash npm install react-native-device-activity ``` ```json // app.json { "plugins": [ ["expo-build-properties", { "ios": { "deploymentTarget": "15.1" } }], [ "react-native-device-activity", { "appleTeamId": "ABCDE12345", "appGroup": "group.com.myapp.screentime" } ] ] } ``` ```bash npx expo prebuild --platform ios ``` --- ## `isAvailable` Returns `true` only on iOS 15+ with the native module loaded; safe to call on Android without crashing. ```typescript import { isAvailable } from 'react-native-device-activity'; if (!isAvailable()) { console.log('Screen Time APIs not available on this platform/version'); return; } ``` --- ## `requestAuthorization` / `revokeAuthorization` / `getAuthorizationStatus` `requestAuthorization` prompts the user for Screen Time permission (individual or child account). `getAuthorizationStatus` synchronously returns the current status (0 = notDetermined, 1 = denied, 2 = approved). `revokeAuthorization` revokes the permission and returns the updated status. ```typescript import { requestAuthorization, revokeAuthorization, getAuthorizationStatus, AuthorizationStatus, } from 'react-native-device-activity'; // Request permission on mount useEffect(() => { async function setup() { const status = getAuthorizationStatus(); if (status === AuthorizationStatus.notDetermined) { try { await requestAuthorization('individual'); // or 'child' } catch (e) { console.error('Authorization failed:', e); } } } setup(); }, []); // Revoke later const handleRevoke = async () => { const newStatus = await revokeAuthorization(); console.log('Status after revoke:', newStatus); // 0 | 1 | 2 }; ``` --- ## `useAuthorizationStatus` A React hook that reactively tracks authorization status changes (including changes made outside the app on next foreground). ```typescript import { useAuthorizationStatus, AuthorizationStatus } from 'react-native-device-activity'; function AuthBanner() { const authorizationStatus = useAuthorizationStatus(); const label = { [AuthorizationStatus.notDetermined]: 'Not determined', [AuthorizationStatus.denied]: 'Denied', [AuthorizationStatus.approved]: 'Approved ✅', }[authorizationStatus]; return <Text>{label}</Text>; } ``` --- ## `pollAuthorizationStatus` Polls `getAuthorizationStatus` at an interval until a non-`notDetermined` status is returned or max attempts is reached. Useful immediately after `requestAuthorization` since the native API can lag. ```typescript import { requestAuthorization, pollAuthorizationStatus, AuthorizationStatus } from 'react-native-device-activity'; const handleAuth = async () => { await requestAuthorization('individual'); const finalStatus = await pollAuthorizationStatus({ pollIntervalMs: 300, maxAttempts: 15, }); if (finalStatus === AuthorizationStatus.approved) { console.log('Ready to monitor'); } else { console.warn('User denied or timed out:', finalStatus); } }; ``` --- ## `onAuthorizationStatusChange` Subscribes to native authorization status change events. Returns an `EventSubscription` — call `.remove()` to unsubscribe. ```typescript import { onAuthorizationStatusChange, AuthorizationStatus } from 'react-native-device-activity'; useEffect(() => { const sub = onAuthorizationStatusChange(({ authorizationStatus }) => { if (authorizationStatus === AuthorizationStatus.approved) { console.log('User approved Screen Time access'); } }); return () => sub.remove(); }, []); ``` --- ## `DeviceActivitySelectionView` An inline native iOS SwiftUI view that lets the user pick apps, app categories, and web domains to monitor or block. Prone to crashes on large category browsing — always provide a fallback UI behind it. ```typescript import { useState } from 'react'; import { Modal, View } from 'react-native'; import { DeviceActivitySelectionView } from 'react-native-device-activity'; function AppPicker({ visible, onDismiss }) { const [selection, setSelection] = useState<string | null>(null); return ( <Modal visible={visible} animationType="slide" presentationStyle="pageSheet" onRequestClose={onDismiss}> {/* Fallback behind native view in case of SwiftUI crash */} <View style={{ flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center' }}> <Text>Loading picker…</Text> </View> <DeviceActivitySelectionView style={{ flex: 1, width: '100%', position: 'absolute', top: 0, bottom: 0 }} familyActivitySelection={selection} headerText="Choose apps to monitor" footerText="Your selection is private" onSelectionChange={(event) => { // event.nativeEvent: { familyActivitySelection, applicationCount, categoryCount, webDomainCount, includeEntireCategory } console.log(`Selected ${event.nativeEvent.applicationCount} apps`); setSelection(event.nativeEvent.familyActivitySelection); onDismiss(); }} /> </Modal> ); } ``` --- ## `DeviceActivitySelectionSheetView` Uses Apple's native `.familyActivityPicker(isPresented:)` sheet with built-in Cancel/Done navigation controls. Mount it as a 1×1 invisible anchor; the native side presents the full-screen sheet automatically. ```typescript import { useState } from 'react'; import { DeviceActivitySelectionSheetView } from 'react-native-device-activity'; function NativeSheetPicker() { const [visible, setVisible] = useState(false); const [selection, setSelection] = useState<string | null>(null); return ( <> <Button title="Pick apps" onPress={() => setVisible(true)} /> {visible && ( <DeviceActivitySelectionSheetView style={{ width: 1, height: 1, position: 'absolute' }} familyActivitySelection={selection} onDismissRequest={() => setVisible(false)} onSelectionChange={(event) => { setSelection(event.nativeEvent.familyActivitySelection); }} /> )} </> ); } ``` --- ## `DeviceActivitySelectionViewPersisted` / `DeviceActivitySelectionSheetViewPersisted` Persisted variants that store and retrieve the `familyActivitySelection` token on the native side by a string ID. Avoids passing large base64 tokens through JavaScript. ```typescript import { DeviceActivitySelectionSheetViewPersisted } from 'react-native-device-activity'; // The selection is auto-persisted to UserDefaults under the key "my-blocked-apps" function PersistedPicker({ visible, onDismiss }) { return ( <> {visible && ( <DeviceActivitySelectionSheetViewPersisted style={{ width: 1, height: 1, position: 'absolute' }} familyActivitySelectionId="my-blocked-apps" includeEntireCategory={true} // Required for correct category whitelisting onDismissRequest={onDismiss} onSelectionChange={(event) => { // event.nativeEvent: { applicationCount, categoryCount, webDomainCount, includeEntireCategory } console.log('Saved selection with', event.nativeEvent.applicationCount, 'apps'); }} /> )} </> ); } ``` --- ## `setFamilyActivitySelectionId` / `getFamilyActivitySelectionId` Manually store and retrieve a `familyActivitySelection` token by a named ID in the shared `UserDefaults`. This is necessary when using `blockSelection` or `configureActions` with `familyActivitySelectionId`. ```typescript import { setFamilyActivitySelectionId, getFamilyActivitySelectionId, } from 'react-native-device-activity'; // After the user picks apps, store the token under a stable ID const handleSelectionChange = (event) => { setFamilyActivitySelectionId({ id: 'evening-block-selection', familyActivitySelection: event.nativeEvent.familyActivitySelection, }); }; // Later, verify it is stored const token = getFamilyActivitySelectionId('evening-block-selection'); console.log('Stored token:', token ? token.substring(0, 20) + '...' : 'none'); ``` --- ## `startMonitoring` Starts a `DeviceActivityMonitor` session for the given activity name, schedule, and optional threshold events. The schedule uses `DateComponents` (Apple's calendar components). Up to 20 simultaneous monitors are allowed. ```typescript import { startMonitoring } from 'react-native-device-activity'; await startMonitoring( 'evening-block', // activityName: unique string identifier { intervalStart: { hour: 19, minute: 0 }, // 7:00 PM daily intervalEnd: { hour: 23, minute: 59 }, repeats: true, warningTime: { minute: 5 }, // fire warning callbacks 5 min before }, [ { eventName: 'reached_30_minutes', familyActivitySelection: token, // base64 token from DeviceActivitySelectionView threshold: { minute: 30 }, includesPastActivity: false, }, { eventName: 'reached_60_minutes', familyActivitySelection: token, threshold: { minute: 60 }, }, ], ); ``` --- ## `stopMonitoring` Stops one or more named monitors. If called with no arguments, stops all active monitors. ```typescript import { stopMonitoring } from 'react-native-device-activity'; // Stop a specific monitor stopMonitoring(['evening-block']); // Stop all monitors stopMonitoring(); ``` --- ## `getActivities` / `useActivities` `getActivities` synchronously returns the names of currently active (running) monitors. `useActivities` is a React hook that re-renders on changes and also returns a `refresh` callback. ```typescript import { useActivities, stopMonitoring, cleanUpAfterActivity } from 'react-native-device-activity'; function ActiveMonitorsList() { const [activities, refresh] = useActivities(); return ( <> {activities.map((name) => ( <View key={name} style={{ flexDirection: 'row', justifyContent: 'space-between' }}> <Text>{name}</Text> <Button title="Stop" onPress={() => { cleanUpAfterActivity(name); // removes action/event keys from UserDefaults stopMonitoring([name]); refresh(); }} /> </View> ))} </> ); } ``` --- ## `configureActions` Stores an array of `Action` objects in `UserDefaults` under a key tied to an activity name and callback name (e.g. `actions_for_evening-block_intervalDidStart`). The native ActivityMonitor extension reads and executes these actions in the background when the callback fires. ```typescript import { configureActions } from 'react-native-device-activity'; // Block apps when the interval starts configureActions({ activityName: 'evening-block', callbackName: 'intervalDidStart', actions: [ { type: 'blockSelection', familyActivitySelectionId: 'evening-block-selection', shieldId: 'evening-shield', }, ], }); // Unblock when the interval ends configureActions({ activityName: 'evening-block', callbackName: 'intervalDidEnd', actions: [ { type: 'unblockSelection', familyActivitySelectionId: 'evening-block-selection' }, { type: 'sendNotification', payload: { title: 'Good night!', body: 'Screen time restrictions lifted.' } }, ], }); // On a specific threshold event, send an HTTP webhook and a notification configureActions({ activityName: 'time-tracker', callbackName: 'eventDidReachThreshold', eventName: 'reached_30_minutes', actions: [ { type: 'sendHttpRequest', url: 'https://api.myapp.com/webhooks/usage', options: { method: 'POST', body: { activityName: 'time-tracker', minutesReached: 30 }, headers: { 'Authorization': 'Bearer MY_TOKEN' }, }, }, { type: 'sendNotification', payload: { title: '30 minutes reached', body: 'You have used {applicationOrDomainDisplayName} for 30 minutes.', sound: 'default', interruptionLevel: 'active', }, skipIfAlreadyTriggeredWithinMS: 60_000, // don't re-fire within 1 minute }, ], }); ``` --- ## `blockSelection` / `unblockSelection` Immediately apply or remove a `ManagedSettings` block for a saved selection ID or an inline token. The block persists until explicitly cleared. ```typescript import { blockSelection, unblockSelection, isShieldActive } from 'react-native-device-activity'; // Block by persisted selection ID blockSelection({ activitySelectionId: 'evening-block-selection' }, 'manual-block'); console.log('Shield active:', isShieldActive()); // true // Unblock after 5 seconds setTimeout(() => { unblockSelection({ activitySelectionId: 'evening-block-selection' }, 'manual-unblock'); console.log('Shield active:', isShieldActive()); // false }, 5000); ``` --- ## `enableBlockAllMode` / `disableBlockAllMode` / `resetBlocks` `enableBlockAllMode` blocks all apps/websites (no selection needed). `disableBlockAllMode` removes the all-apps block. `resetBlocks` clears the entire blocklist including whitelists and block-all mode. ```typescript import { enableBlockAllMode, disableBlockAllMode, resetBlocks } from 'react-native-device-activity'; // Block everything during an exam enableBlockAllMode('exam-started'); // After exam, disable block-all but keep selection blocks disableBlockAllMode('exam-ended'); // Full reset — clear everything resetBlocks('end-of-day'); ``` --- ## `updateShield` / `updateShieldWithId` Sets the `ShieldConfiguration` (visual appearance) and `ShieldActions` (button behaviors) stored in `UserDefaults`. The native `ShieldConfiguration` and `ShieldAction` extensions read these values when rendering the block screen. ```typescript import { updateShield, UIBlurEffectStyle } from 'react-native-device-activity'; updateShield( { title: 'Take a Break 🌙', titleColor: { red: 255, green: 255, blue: 255 }, subtitle: 'This app is blocked until midnight.', subtitleColor: { red: 200, green: 200, blue: 200 }, backgroundBlurStyle: UIBlurEffectStyle.systemMaterialDark, iconSystemName: 'moon.stars.fill', // SF Symbols name primaryButtonLabel: 'OK', primaryButtonLabelColor: { red: 255, green: 255, blue: 255 }, primaryButtonBackgroundColor: { red: 50, green: 50, blue: 200 }, secondaryButtonLabel: 'Open App', secondaryButtonLabelColor: { red: 180, green: 180, blue: 180 }, }, { primary: { behavior: 'close' }, secondary: { behavior: 'defer', type: 'unblockPossibleFamilyActivitySelection', }, }, ); ``` --- ## `addSelectionToWhitelistAndUpdateBlock` / `removeSelectionFromWhitelistAndUpdateBlock` / `clearWhitelistAndUpdateBlock` Manage the whitelist: apps in the whitelist are exempt from block-all mode. After any change the block is automatically re-applied to reflect the new whitelist. ```typescript import { enableBlockAllMode, addSelectionToWhitelistAndUpdateBlock, removeSelectionFromWhitelistAndUpdateBlock, clearWhitelistAndUpdateBlock, } from 'react-native-device-activity'; // Block everything, but whitelist a specific selection enableBlockAllMode(); addSelectionToWhitelistAndUpdateBlock( { activitySelectionId: 'allowed-apps' }, 'whitelist-setup', ); // Later, remove from whitelist (block-all applies again for that selection) removeSelectionFromWhitelistAndUpdateBlock( { activitySelectionId: 'allowed-apps' }, 'whitelist-removal', ); // Clear the entire whitelist (block-all now applies to everything) clearWhitelistAndUpdateBlock('full-reset'); ``` --- ## `setWebContentFilterPolicy` / `clearWebContentFilterPolicy` / `isWebContentFilterPolicyActive` Apply Apple's `WebContentSettings` filter policy to block or allow web domains independently of app blocking. ```typescript import { setWebContentFilterPolicy, clearWebContentFilterPolicy, isWebContentFilterPolicyActive, } from 'react-native-device-activity'; // Auto-filter: block adult content, with additional blocked domains setWebContentFilterPolicy({ type: 'auto', domains: ['gambling-site.com'], exceptDomains: ['safe-exception.com'], }); // Allowlist-only: block everything except listed domains setWebContentFilterPolicy({ type: 'all', exceptDomains: ['school-portal.edu', 'wikipedia.org'], }); // Block only specific domains setWebContentFilterPolicy({ type: 'specific', domains: ['distracting-site.com', 'social-media.com'], }); console.log('Filter active:', isWebContentFilterPolicyActive()); // true // Remove web filter clearWebContentFilterPolicy(); ``` --- ## `getEvents` Returns a sorted array of all triggered activity events parsed from `UserDefaults`. Optionally filter by activity name. ```typescript import { getEvents } from 'react-native-device-activity'; // All events, sorted oldest-first const allEvents = getEvents(); /* [ { activityName: 'time-tracker', callbackName: 'eventDidReachThreshold', eventName: 'reached_30_minutes', lastCalledAt: Date(2024-01-15T09:30:00), }, ... ] */ // Filter by activity const trackerEvents = getEvents('time-tracker'); const totalMinutes = trackerEvents .filter(e => e.callbackName === 'eventDidReachThreshold') .reduce((max, e) => { const mins = parseInt(e.eventName?.replace('reached_', '') ?? '0', 10); return Math.max(max, mins); }, 0); console.log(`Max tracked usage: ${totalMinutes} minutes`); ``` --- ## `onDeviceActivityMonitorEvent` Subscribes to real-time `DeviceActivityMonitor` callback events while the app is alive. The `callbackName` matches the monitor lifecycle: `intervalDidStart`, `intervalDidEnd`, `eventDidReachThreshold`, `intervalWillStartWarning`, `intervalWillEndWarning`, `eventWillReachThresholdWarning`. ```typescript import { onDeviceActivityMonitorEvent } from 'react-native-device-activity'; useEffect(() => { const sub = onDeviceActivityMonitorEvent((event) => { // event: { callbackName: CallbackEventName } switch (event.callbackName) { case 'intervalDidStart': console.log('Monitoring interval started — blocks now active'); break; case 'intervalDidEnd': console.log('Monitoring interval ended — blocks lifted'); break; case 'eventDidReachThreshold': console.log('Usage threshold reached'); break; } }); return () => sub.remove(); }, []); ``` --- ## `intersection` / `union` / `difference` / `symmetricDifference` Perform set operations on two `FamilyActivitySelection` tokens or the current blocklist/whitelist. Returns an `ActivitySelectionWithMetadata` (token + counts). ```typescript import { intersection, union, difference, symmetricDifference } from 'react-native-device-activity'; const selA = { activitySelectionToken: tokenA }; const selB = { activitySelectionToken: tokenB }; const common = intersection(selA, selB, { stripToken: true }); // { familyActivitySelection: null, applicationCount: 2, categoryCount: 0, webDomainCount: 0 } const combined = union(selA, selB, { persistAsActivitySelectionId: 'combined-selection', // save result for later use }); const onlyInA = difference(selA, selB, { stripToken: true }); const notInBoth = symmetricDifference(selA, selB, { stripToken: true }); console.log('Apps only in A:', onlyInA?.applicationCount); // Compare current blocklist against a selection const overlap = intersection( { currentBlocklist: true }, selB, { stripToken: true }, ); ``` --- ## `activitySelectionMetadata` / `activitySelectionWithMetadata` / `isEqual` / `isSubsetOf` Inspect a selection's counts or test relationships between selections without full set operations. ```typescript import { activitySelectionMetadata, activitySelectionWithMetadata, isEqual, isSubsetOf, } from 'react-native-device-activity'; const meta = activitySelectionMetadata({ activitySelectionToken: tokenA }); // { applicationCount: 5, categoryCount: 1, webDomainCount: 0, includeEntireCategory: false } const withToken = activitySelectionWithMetadata({ activitySelectionToken: tokenA }); // { familyActivitySelection: '...base64...', applicationCount: 5, ... } // Check if two selections are identical const same = isEqual( { activitySelectionToken: tokenA }, { activitySelectionId: 'saved-selection' }, ); // Check if a small selection is a subset of a larger one const isContained = isSubsetOf( { activitySelectionToken: smallToken }, { currentBlocklist: true }, ); console.log('All blocked?', isContained); ``` --- ## `userDefaultsSet` / `userDefaultsGet` / `userDefaultsRemove` / `userDefaultsAll` / `userDefaultsClear` / `userDefaultsClearWithPrefix` Low-level access to the shared App Group `UserDefaults` store. Used internally by `configureActions` and `updateShield`; also useful for custom data sharing between the main app and native extensions. ```typescript import { userDefaultsSet, userDefaultsGet, userDefaultsRemove, userDefaultsAll, userDefaultsClear, userDefaultsClearWithPrefix, } from 'react-native-device-activity'; // Store custom config userDefaultsSet('myapp.config', { theme: 'dark', userId: 'abc123' }); // Retrieve const config = userDefaultsGet<{ theme: string; userId: string }>('myapp.config'); console.log(config?.theme); // 'dark' // Remove a single key userDefaultsRemove('myapp.config'); // Dump all stored values (useful for debugging) const all = userDefaultsAll(); console.log(JSON.stringify(all, null, 2)); // Clear all keys with a given prefix (e.g., all action configs for one activity) userDefaultsClearWithPrefix('actions_for_evening-block'); // Nuclear option — wipe entire UserDefaults store userDefaultsClear(); ``` --- ## `cleanUpAfterActivity` Convenience function that clears all `UserDefaults` keys for a given activity name (both `actions_for_${name}` and `events_${name}` prefixes). Call this before stopping monitoring to avoid stale data. ```typescript import { cleanUpAfterActivity, stopMonitoring } from 'react-native-device-activity'; const ACTIVITY = 'evening-block'; // Clean up stored actions and events, then stop cleanUpAfterActivity(ACTIVITY); stopMonitoring([ACTIVITY]); ``` --- ## `moveFile` / `copyFile` / `getAppGroupFileDirectory` File utility functions that operate within the shared App Group container, enabling file sharing between the main app and native extensions (e.g., storing custom shield images). ```typescript import { getAppGroupFileDirectory, copyFile } from 'react-native-device-activity'; // Get the shared directory path const sharedDir = getAppGroupFileDirectory(); // e.g. /private/var/mobile/Containers/Shared/AppGroup/ABC.../ // Copy a local image into the shared directory for use as a shield icon const sourceUri = FileSystem.documentDirectory + 'shield-icon.png'; const destUri = sharedDir + 'shield-icon.png'; copyFile(sourceUri, destUri, true /* overwrite */); // Reference the relative path in ShieldConfiguration updateShield( { iconAppGroupRelativePath: 'shield-icon.png' }, { primary: { behavior: 'close' } }, ); ``` --- ## `reloadDeviceActivityCenter` / `refreshManagedSettingsStore` / `clearAllManagedSettingsStoreSettings` Utility reset functions for troubleshooting. `reloadDeviceActivityCenter` re-initializes the monitor process. `refreshManagedSettingsStore` forces re-reading of the managed settings. `clearAllManagedSettingsStoreSettings` removes all active blocks, whitelists, and web filters. ```typescript import { reloadDeviceActivityCenter, refreshManagedSettingsStore, clearAllManagedSettingsStoreSettings, } from 'react-native-device-activity'; // If events stop arriving, try reloading the center reloadDeviceActivityCenter(); // If shield state seems stale refreshManagedSettingsStore(); // Full nuclear reset of all applied settings (does not clear UserDefaults) clearAllManagedSettingsStoreSettings(); ``` --- ## Summary `react-native-device-activity` is the go-to library for building parental control, digital wellbeing, and focus-mode features in React Native iOS apps. The primary use cases are: (1) **scheduled app blocking** — using `startMonitoring` + `configureActions` to automatically block/unblock selections at specific times or after usage thresholds; (2) **real-time usage tracking** — setting up threshold events that fire webhooks, notifications, or app-opens when users reach N minutes of screen time; (3) **whitelist-based focus mode** — calling `enableBlockAllMode` with an exception whitelist so only permitted apps remain accessible; and (4) **web content filtering** — applying `setWebContentFilterPolicy` to restrict or allow specific domains during defined periods, independent of app blocking. Integration follows a predictable pattern: request authorization → present `DeviceActivitySelectionView` to collect the user's app selection → persist the selection with `setFamilyActivitySelectionId` → call `configureActions` for each lifecycle callback → call `startMonitoring` with the desired schedule. All action execution happens inside Apple's native extension processes, meaning blocks and notifications fire reliably even when the app is terminated. The shared `UserDefaults` App Group is the communication channel between JS and those extensions, so `configureActions` and `updateShield` must be called before the schedule's `intervalDidStart` fires. For debugging, `getEvents()` provides a full history of triggered callbacks, and the various `userDefaults*` utilities let you inspect or reset all stored state.