react-native-app-lock
v1.0.1
Published
App lock for React Native — Android Accessibility Service + iOS Screen Time (FamilyControls). List installed apps, lock/unlock with toggles, works on both platforms.
Maintainers
Readme
react-native-app-lock
App lock for React Native — Android Accessibility Service + iOS Screen Time (FamilyControls). List installed apps, lock/unlock with toggles, works on both platforms.
Features
- Android: lists all installed apps, locks/unlocks them via Accessibility Service, shows app icons
- iOS: blocks selected apps via Apple's Screen Time / FamilyControls API
- Drop-in
<AppLockScreen>component with theming support useAppLock()hook for custom UIs- Low-level
AppLockbridge for direct native calls
Installation
npm install react-native-app-lock
# or
yarn add react-native-app-lockAndroid setup
- Add the Accessibility Service declaration to
AndroidManifest.xml:
<service
android:name="com.yourapp.AppLockService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>- Rebuild the app:
npx react-native run-androidiOS setup
- Add the
FamilyControlscapability in Xcode → Signing & Capabilities. - Rebuild:
npx react-native run-iosNote: FamilyControls requires a physical device — it does not work on the simulator.
Usage
Option 1 — Drop-in screen component
The easiest way. Renders a full app-lock management screen ready to push onto your navigator.
import { AppLockScreen } from 'react-native-app-lock';
export function SettingsScreen({ navigation }) {
return (
<AppLockScreen
onBack={() => navigation.goBack()}
colors={{
background: '#FFFFFF',
text: '#111827',
textSecondary: '#6B7280',
border: '#E5E7EB',
inputBackground: '#F9FAFB',
inputBorder: '#D1D5DB',
primary: '#6366F1',
}}
fonts={{
regular: 'Inter-Regular',
medium: 'Inter-Medium',
bold: 'Inter-Bold',
}}
/>
);
}AppLockScreen props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| onBack | () => void | Yes | Called when the user taps the back button |
| colors | AppLockColors | Yes | Theme colors (see below) |
| fonts | AppLockFonts | No | Font families — falls back to System |
AppLockColors
| Key | Description |
|-----|-------------|
| background | Screen/row background |
| text | Primary text |
| textSecondary | Secondary/placeholder text |
| border | Dividers, toggle off-state |
| inputBackground | Search bar background |
| inputBorder | Search bar border |
| primary | Accent color (toggle on-state, buttons) |
AppLockFonts
| Key | Description |
|-----|-------------|
| regular | Body text font family |
| medium | Medium-weight font family |
| bold | Bold font family |
Option 2 — useAppLock() hook
Use this when you want to build your own UI.
import { useAppLock } from 'react-native-app-lock';
export function MyAppLockUI() {
const lock = useAppLock();
if (lock.loading) return <ActivityIndicator />;
return (
<FlatList
data={lock.apps}
keyExtractor={(item) => item.packageName}
renderItem={({ item }) => (
<View>
<Text>{item.appName}</Text>
<Switch
value={lock.lockedSet.has(item.packageName)}
onValueChange={(val) =>
val ? lock.lockApp(item.packageName) : lock.unlockApp(item.packageName)
}
/>
</View>
)}
/>
);
}Hook return values
| Property | Platform | Type | Description |
|----------|----------|------|-------------|
| loading | Both | boolean | True while initial data loads |
| lockApp(pkg) | Android | (pkg: string) => Promise<void> | Lock a single app by package name |
| unlockApp(pkg) | Android | (pkg: string) => Promise<void> | Unlock a single app by package name |
| unlockAll() | Both | () => Promise<void> | Remove all locks |
| apps | Android | InstalledApp[] | List of all installed apps |
| lockedSet | Android | Set<string> | Currently locked package names |
| hasPermission | Android | boolean | Whether Accessibility Service is granted |
| setLockedApps(pkgs) | Android | (pkgs: string[]) => Promise<void> | Replace the entire locked list at once |
| savingPkg | Android | string \| null | Package currently being saved (for loading state) |
| openAccessibilitySettings() | Android | () => Promise<void> | Open system Accessibility Settings |
| openOverlaySettings() | Android | () => Promise<void> | Open overlay/draw-over-apps settings |
| iosAuthStatus | iOS | AuthorizationStatus | 'approved' \| 'denied' \| 'notDetermined' |
| requestIosAuthorization() | iOS | () => Promise<void> | Show the Screen Time permission dialog |
| pickApps() | iOS | () => Promise<string \| null> | Open the native FamilyActivityPicker |
| shieldFromEncoded(encoded) | iOS | (encoded: string) => Promise<void> | Shield apps from a picker selection string |
| reApplyShields() | iOS | () => Promise<void> | Re-apply saved shields on app launch |
iOS flow example
import { useAppLock } from 'react-native-app-lock';
import { useEffect } from 'react';
function AppRoot() {
const lock = useAppLock();
// Re-apply shields every time the app opens
useEffect(() => {
lock.reApplyShields();
}, []);
const handleLock = async () => {
const encoded = await lock.pickApps(); // shows native picker
if (encoded) {
await lock.shieldFromEncoded(encoded);
}
};
if (lock.iosAuthStatus !== 'approved') {
return <Button title="Grant Permission" onPress={lock.requestIosAuthorization} />;
}
return (
<>
<Button title="Select Apps to Lock" onPress={handleLock} />
<Button title="Remove All Locks" onPress={lock.unlockAll} />
</>
);
}Option 3 — Low-level AppLock bridge
For direct native calls without any React state.
import AppLock from 'react-native-app-lock';
// or named:
import { AppLock } from 'react-native-app-lock';
// Cross-platform helpers
await AppLock.hasRequiredPermissions(); // boolean
await AppLock.requestPermissions(); // boolean
await AppLock.lockApps(['com.instagram.android', 'com.tiktok.android']); // Android
await AppLock.unlockAll();
// Android-specific
await AppLock.android.getInstalledApps();
await AppLock.android.lockApp('com.instagram.android');
await AppLock.android.unlockApp('com.instagram.android');
await AppLock.android.getLockedApps(); // string[]
await AppLock.android.setLockedApps([...]); // replace all
await AppLock.android.isAccessibilityEnabled();
await AppLock.android.openAccessibilitySettings();
await AppLock.android.canDrawOverlays();
await AppLock.android.openOverlaySettings();
// iOS-specific
await AppLock.ios.getAuthorizationStatus(); // AuthorizationStatus
await AppLock.ios.requestAuthorization();
await AppLock.ios.presentAppPicker(); // encoded string | null
await AppLock.ios.shieldAppsFromEncoded(encoded);
await AppLock.ios.shieldSelectedApps(); // re-apply last saved selection
await AppLock.ios.clearAllShields();
await AppLock.ios.getSavedSelection(); // encoded string | nullTypes
interface InstalledApp {
packageName: string; // e.g. "com.instagram.android"
appName: string; // e.g. "Instagram"
isSystemApp: boolean;
icon: string; // base64-encoded PNG
}
type AuthorizationStatus = 'approved' | 'denied' | 'notDetermined';License
MIT
