Building a parental control app, enterprise security solution, or device management tool? Learn how to implement admin permissions in Flutter that can prevent app uninstallation and provide device control capabilities. This comprehensive guide walks you through creating a complete working implementation with proper security considerations.
Admin permissions, also known as Device Administrator privileges, grant your Android app elevated system access including the ability to lock the device, wipe data, and crucially - prevent users from uninstalling your app through normal means. These permissions are essential for legitimate security applications, parental controls, and enterprise mobile device management (MDM) solutions.
In this tutorial, we'll build a Flutter app with three core functions: Enable Admin Permissions, Disable Admin Permissions, and Lock Device. You'll learn both the native Android implementation and Flutter integration required to make this work seamlessly.
⚠️ Important Disclaimer: Admin permissions are powerful security features that should only be used in legitimate applications with proper user consent. Always be transparent about what permissions you're requesting and why. Misuse of these permissions can result in your app being flagged as malware and removed from app stores.
Note: This functionality is Android-specific. iOS has different security models and doesn't provide equivalent admin permission APIs to third-party developers.
Project Setup
Let's start by creating a new Flutter project and configuring the necessary dependencies and permissions.
Creating the Flutter Project
First, create a new Flutter project:
flutter create admin_permission
cd admin_permission
Dependencies Configuration
Update your pubspec.yaml
file. For this implementation, we'll use Flutter's built-in platform channels, so no additional dependencies are required:
name: admin_permission
description: Flutter app demonstrating admin permissions implementation
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
Android Manifest Configuration
The most critical setup step is configuring your AndroidManifest.xml
file. Replace the contents of android/app/src/main/AndroidManifest.xml
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Essential permissions for Device Admin functionality -->
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.USES_POLICY_FORCE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="admin_permission">
<!-- Main Flutter Activity -->
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- Device Admin Receiver - Critical component -->
<receiver
android:name=".DeviceAdminReceiverImpl"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:exported="true">
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/device_admin_receiver" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
</application>
<!-- Required for text processing queries -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
Project Structure Organization
Your project structure should look like this after setup:
android/app/src/main/
├── AndroidManifest.xml
├── kotlin/com/example/admin_permission/
│ ├── MainActivity.kt
│ └── DeviceAdminReceiverImpl.kt
└── res/xml/
└── device_admin_receiver.xml
💡 Tip: If the xml
folder doesn't exist in android/app/src/main/res/
, create it manually. This is where we'll place our device admin configuration.
Native Android Implementation
Now we'll implement the core Android functionality that handles admin permissions. This involves creating a DeviceAdminReceiver and configuring the admin policies.
Device Admin Configuration File
First, create the device admin configuration. Create android/app/src/main/res/xml/device_admin_receiver.xml
:
<?xml version="1.0" encoding="utf-8"?>
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
<uses-policies>
<!-- Allow the app to force device lock -->
<force-lock />
<!-- Allow the app to perform factory reset (use carefully!) -->
<wipe-data />
<!-- Allow the app to reset device password -->
<reset-password />
<!-- Allow the app to disable camera -->
<disable-camera />
</uses-policies>
</device-admin>
⚠️ Security Warning: Only include policies your app actually needs. Each policy increases the permission scope and may concern users.
DeviceAdminReceiver Implementation
Create android/app/src/main/kotlin/com/example/admin_permission/DeviceAdminReceiverImpl.kt
:
package com.example.admin_permission
import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast
/**
* DeviceAdminReceiver handles admin permission lifecycle events
* This class receives callbacks when admin permissions are granted/revoked
*/
class DeviceAdminReceiverImpl : DeviceAdminReceiver() {
/**
* Called when device admin is successfully enabled
* Show user confirmation that admin privileges are active
*/
override fun onEnabled(context: Context, intent: Intent) {
Toast.makeText(context, "Device Admin enabled", Toast.LENGTH_SHORT).show()
}
/**
* Called when device admin is disabled/revoked
* Inform user that admin privileges are no longer active
*/
override fun onDisabled(context: Context, intent: Intent) {
Toast.makeText(context, "Device Admin disabled", Toast.LENGTH_SHORT).show()
}
/**
* Called when user attempts to disable admin from system settings
* Return warning message to display to user before disabling
* This is your chance to explain why admin access is important
*/
override fun onDisableRequested(context: Context, intent: Intent): CharSequence {
return "Warning: Disabling admin will allow this app to be uninstalled and " +
"remove device protection features."
}
}
MainActivity Method Channel Setup
Update android/app/src/main/kotlin/com/example/admin_permission/MainActivity.kt
:
package com.example.admin_permission
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "device_admin"
private lateinit var dpm: DevicePolicyManager
private lateinit var adminComponent: ComponentName
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Initialize Device Policy Manager and Admin Component
dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
adminComponent = ComponentName(this, DeviceAdminReceiverImpl::class.java)
// Set up method channel for Flutter-Android communication
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
/**
* Enable admin permissions
* Launches system dialog to request admin privileges
*/
"enableAdmin" -> {
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN).apply {
putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, adminComponent)
putExtra(
DevicePolicyManager.EXTRA_ADD_EXPLANATION,
"This app requires device admin to protect against " +
"uninstallation and enable security features."
)
}
startActivity(intent)
result.success(true)
}
/**
* Disable admin permissions
* Programmatically revokes admin access
*/
"disableAdmin" -> {
if (dpm.isAdminActive(adminComponent)) {
dpm.removeActiveAdmin(adminComponent)
result.success(true)
} else {
result.success(false)
}
}
/**
* Check if app currently has admin permissions
* Returns boolean status
*/
"isAdmin" -> {
val isAdmin = dpm.isAdminActive(adminComponent)
result.success(isAdmin)
}
/**
* Lock device immediately
* Only works if app has admin permissions
*/
"lockNow" -> {
if (dpm.isAdminActive(adminComponent)) {
dpm.lockNow()
result.success(true)
} else {
result.error("NOT_ADMIN", "App is not device admin", null)
}
}
else -> result.notImplemented()
}
}
}
}
Flutter Service Implementation
Now we'll create the Flutter service layer that communicates with our native Android code through method channels.
AdminPermissionService Class
Create lib/admin_permission_service.dart
:
import 'package:flutter/services.dart';
/**
* Service class for managing device admin permissions
* Provides high-level API for Flutter app to interact with admin functionality
*/
class AdminPermissionService {
// Method channel for communicating with native Android code
static const MethodChannel _channel = MethodChannel('device_admin');
/**
* Request admin permissions from user
* Opens system dialog for user to grant admin privileges
* Returns: true if request was initiated successfully
*/
static Future<bool> enableAdmin() async {
try {
final bool result = await _channel.invokeMethod('enableAdmin');
return result;
} on PlatformException catch (e) {
print("Error enabling admin: ${e.code} - ${e.message}");
return false;
}
}
/**
* Programmatically disable admin permissions
* Revokes admin access without user interaction
* Returns: true if admin was successfully disabled
*/
static Future<bool> disableAdmin() async {
try {
final bool result = await _channel.invokeMethod('disableAdmin');
return result;
} on PlatformException catch (e) {
print("Error disabling admin: ${e.code} - ${e.message}");
return false;
}
}
/**
* Check current admin permission status
* Returns: true if app currently has admin privileges
*/
static Future<bool> isAdmin() async {
try {
final bool result = await _channel.invokeMethod('isAdmin');
return result;
} on PlatformException catch (e) {
print("Error checking admin status: ${e.code} - ${e.message}");
return false;
}
}
/**
* Lock device immediately
* Requires admin permissions to work
* Returns: true if device was locked successfully
* Throws: Exception if app doesn't have admin permissions
*/
static Future<bool> lockDevice() async {
try {
final bool result = await _channel.invokeMethod('lockNow');
return result;
} on PlatformException catch (e) {
if (e.code == 'NOT_ADMIN') {
throw Exception('Admin permissions required to lock device');
}
print("Error locking device: ${e.code} - ${e.message}");
return false;
}
}
}
Error Handling
The service includes comprehensive error handling for common scenarios:
- Network/System errors: Caught and logged with meaningful messages
- Permission errors: Specific handling for admin-related failures
- Platform exceptions: Proper exception propagation to UI layer
💡 Best Practice: Always wrap platform channel calls in try-catch blocks to handle potential native code errors gracefully.
UI Implementation
Let's create a clean, functional UI that demonstrates all admin permission capabilities with proper state management.
Complete Main Application
Replace the contents of lib/main.dart
:
import 'package:flutter/material.dart';
import 'admin_permission_service.dart';
void main() {
runApp(const AdminPermissionApp());
}
class AdminPermissionApp extends StatelessWidget {
const AdminPermissionApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Admin Permission Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const AdminPermissionScreen(),
);
}
}
class AdminPermissionScreen extends StatefulWidget {
const AdminPermissionScreen({super.key});
@override
State<AdminPermissionScreen> createState() => _AdminPermissionScreenState();
}
class _AdminPermissionScreenState extends State<AdminPermissionScreen> {
bool _isAdmin = false;
bool _isLoading = false;
String _statusMessage = "Checking admin status...";
@override
void initState() {
super.initState();
_checkAdminStatus();
}
/**
* Check current admin permission status and update UI
*/
Future<void> _checkAdminStatus() async {
setState(() {
_isLoading = true;
_statusMessage = "Checking admin status...";
});
try {
final bool isAdmin = await AdminPermissionService.isAdmin();
setState(() {
_isAdmin = isAdmin;
_statusMessage = isAdmin
? "✅ Admin permissions enabled"
: "❌ Admin permissions disabled";
_isLoading = false;
});
} catch (e) {
setState(() {
_statusMessage = "Error checking status: $e";
_isLoading = false;
});
}
}
/**
* Request admin permissions from user
*/
Future<void> _enableAdmin() async {
setState(() {
_isLoading = true;
_statusMessage = "Requesting admin permissions...";
});
try {
await AdminPermissionService.enableAdmin();
_showSnackBar("Admin permission request sent. Please check system settings.");
// Wait a moment then check status (user might have granted immediately)
Future.delayed(const Duration(seconds: 2), () {
_checkAdminStatus();
});
} catch (e) {
setState(() {
_statusMessage = "Failed to request admin permissions";
_isLoading = false;
});
_showSnackBar("Error requesting admin permissions: $e");
}
}
/**
* Disable admin permissions programmatically
*/
Future<void> _disableAdmin() async {
setState(() {
_isLoading = true;
_statusMessage = "Disabling admin permissions...";
});
try {
final bool success = await AdminPermissionService.disableAdmin();
if (success) {
_showSnackBar("Admin permissions disabled successfully");
_checkAdminStatus();
} else {
setState(() {
_statusMessage = "Failed to disable admin permissions";
_isLoading = false;
});
}
} catch (e) {
setState(() {
_statusMessage = "Error disabling admin permissions";
_isLoading = false;
});
_showSnackBar("Error: $e");
}
}
/**
* Lock device using admin permissions
*/
Future<void> _lockDevice() async {
if (!_isAdmin) {
_showSnackBar("Admin permissions required to lock device");
return;
}
try {
await AdminPermissionService.lockDevice();
_showSnackBar("Device lock initiated");
} catch (e) {
_showSnackBar("Failed to lock device: $e");
}
}
/**
* Show snackbar message to user
*/
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 3),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Device Admin Permissions'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Status indicator card
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Icon(
_isAdmin ? Icons.security : Icons.security_outlined,
size: 48,
color: _isAdmin ? Colors.green : Colors.grey,
),
const SizedBox(height: 8),
Text(
_statusMessage,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
if (_isLoading)
const Padding(
padding: EdgeInsets.only(top: 16.0),
child: CircularProgressIndicator(),
),
],
),
),
),
const SizedBox(height: 32),
// Action buttons
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading ? null : _enableAdmin,
child: const Text('Enable Admin Permissions'),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading || !_isAdmin ? null : _disableAdmin,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
),
child: const Text('Disable Admin Permissions'),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading || !_isAdmin ? null : _lockDevice,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text('Lock Device Now'),
),
),
const SizedBox(height: 32),
// Refresh status button
TextButton(
onPressed: _isLoading ? null : _checkAdminStatus,
child: const Text('Refresh Status'),
),
// Help text
const Padding(
padding: EdgeInsets.only(top: 24.0),
child: Text(
'Note: Admin permissions prevent app uninstallation and '
'enable device control features. Use responsibly.',
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
),
],
),
),
);
}
}
Key UI Features
- Real-time status updates: Shows current admin permission state
- Loading indicators: Provides feedback during async operations
- Smart button states: Disables inappropriate actions based on current state
- Error handling: Displays user-friendly error messages via SnackBars
- Responsive design: Works well on different screen sizes
Testing and Verification
Testing Admin Permission Requests
- Build and install your app on a physical Android device (admin permissions don't work reliably in emulators)
- Tap "Enable Admin Permissions" - this should open the system admin permission dialog
- Grant the permission and verify the status updates to show admin enabled
- Test device lock functionality by tapping the lock button
- Verify uninstall protection by trying to uninstall the app from system settings
Common Issues and Troubleshooting
Issue: Admin permission dialog doesn't appear
- Solution: Check AndroidManifest.xml configuration and ensure all required permissions are present
Issue: "NOT_ADMIN" error when trying to lock device
- Solution: Verify admin permissions are actually granted by checking app in Settings > Security > Device administrators
Issue: App can still be uninstalled despite admin permissions
- Solution: Some Android versions or OEM customizations may override admin protection
Testing on Different Android Versions
Admin permission behavior varies across Android versions:
- Android 6-8: Standard admin permissions work consistently
- Android 9+: Additional restrictions may apply, especially for non-system apps
- Android 12+: Enhanced security models may limit some admin capabilities
⚠️ Testing Tip: Always test on multiple devices and Android versions, as OEM customizations can affect admin permission behavior.
Conclusion
You've successfully implemented a complete Flutter admin permissions system that can prevent app uninstallation and provide device control capabilities. This implementation includes proper error handling, user feedback, and follows Android security best practices.
Remember to use these powerful permissions responsibly. Always be transparent with users about what permissions you're requesting and why your app needs them. Admin permissions should enhance user security and provide legitimate functionality, not compromise user trust.
The complete source code for this implementation is available on GitHub: flutter-admin-permissions-example
For production applications, thoroughly test across multiple devices and Android versions, ensure compliance with app store policies, and consider implementing additional security measures based on your specific use case requirements.
Top comments (0)