Secure Content
A flutter package which allows Flutter apps to wrap up some widgets with a SecureWidget which can stop user from screen recording or screenshot the widget. Works on both Android & iOS.
Screenshots
Android (screen recording) | iOS (screenshot) | iOS (screen recording) | iOS (app switcher) |
---|---|---|---|
https://user-images.githubusercontent.com/60510869/154502746-830d9198-8f11-46ba-9246-784def00f610.mp4 | https://github.com/user-attachments/assets/0b4e10ac-d592-4b5b-92bf-72f51b2cf570 | https://github.com/user-attachments/assets/b6ef5914-eb3a-4e17-be0c-2f00538cffec |
Features
- 🔒 Screenshot Protection: Prevents users from taking screenshots of sensitive content
- 📹 Screen Recording Protection: Blocks screen recording attempts
- 📱 App Switcher Protection: Secures content in the app switcher preview
- ⚡ Dynamic Security: Enable/disable protection on the fly
- 🎨 Customizable Overlay: Define custom widgets to show when content is protected
- 📢 Event Callbacks: Get notified of screenshot and recording attempts
- 💪 Cross-Platform: Works on both Android & iOS
- 🔄 State Aware: Maintains security across route transitions
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
secure_content: ^1.0.1
Basic Setup
- First, wrap your MaterialApp with Portal widget:
void main() {
runApp(
Portal(
child: MaterialApp(
home: MyHomePage(),
),
),
);
}
- Import the package:
import 'package:secure_content/secure_content.dart';
🚨 Important Note
This package requires wrapping your MaterialApp
with the Portal
widget from the flutter_portal
package. This is a crucial step to make the secure content functionality work properly. Here's how to do it:
import 'package:flutter_portal/flutter_portal.dart';
// In your app's root widget:
return Portal(
child: MaterialApp(
// Your MaterialApp configuration
),
);
If you don't wrap your MaterialApp
with Portal
, you'll encounter the following error:
PortalNotFoundError: Could not find a Portal above this PortalTarget
Usage
Basic Implementation
Wrap any widget that needs to be protected with SecureWidget
:
SecureWidget(
isSecure: true,
builder: (context, onInit, onDispose) => Text(
'This content is protected',
style: Theme.of(context).textTheme.headlineMedium,
),
)
Advanced Implementation
SecureWidget(
isSecure: true,
onScreenshotCaptured: () {
// Handle screenshot attempt
print('Screenshot attempted!');
},
onScreenRecordingStart: () {
// Handle recording start
print('Screen recording started!');
},
onScreenRecordingStop: () {
// Handle recording stop
print('Screen recording stopped!');
},
builder: (context, onInit, onDispose) => YourWidget(),
overlayWidgetBuilder: (context) => BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: const SizedBox(),
),
appSwitcherMenuColor: Colors.black,
protectInAppSwitcherMenu: true,
)
Configuration Options
Parameter | Type | Description |
---|---|---|
isSecure |
bool |
Enable/disable protection |
builder |
Widget Function(BuildContext, VoidCallback, VoidCallback) |
Builder for the protected content |
overlayWidgetBuilder |
Widget Function(BuildContext)? |
Custom overlay when content is protected |
onScreenshotCaptured |
VoidCallback? |
Callback for screenshot attempts |
onScreenRecordingStart |
VoidCallback? |
Callback when recording starts |
onScreenRecordingStop |
VoidCallback? |
Callback when recording stops |
debug |
bool |
Show overlay widget for debugging |
protectInAppSwitcherMenu |
bool |
Enable protection in app switcher |
appSwitcherMenuColor |
Color |
Background color in app switcher |
Protecting Entire App
To protect your entire app on Android:
final secureContent = SecureContent();
// Enable protection
secureContent.preventScreenshotAndroid(true);
// Disable protection
secureContent.preventScreenshotAndroid(false);
Platform-Specific Behavior
Feature Comparison
Feature | iOS | Android |
---|---|---|
Screenshot Prevention | ✅ | ✅ |
Screen Recording Prevention | ✅ (Shows black screen) | ✅ (Shows black screen) |
Screenshot Detection Callback | ✅ | ❌ |
Screen Recording Start Callback | ✅ | ❌ |
Screen Recording Stop Callback | ✅ | ❌ |
App Switcher Protection | ✅ | ✅ |
Custom Overlay Support | ✅ | ✅ |
Dynamic Security Toggle | ✅ | ✅ |
Full App Protection | ✅ (Add widget to top of widget tree) | ✅ |
iOS
- Shows black screen during screen recording
- Prevents screenshots
- Provides callbacks for screenshot and screen recording events
- Customizable protection in app switcher
- Protection is widget-specific
Android
- Blocks screenshots
- Shows black screen during recording
- No callback support for detection events
- Supports full app protection through
preventScreenshotAndroid()
- Protection can be applied globally or widget-specific
Best Practices
-
Platform-Specific Implementation:
- For iOS, utilize callbacks to provide user feedback
- For Android, consider using global protection if needed
- For iOS, use
SecureWidget
at the top of the widget tree, for global protection
// iOS-specific implementation SecureWidget( isSecure: true, onScreenshotCaptured: Platform.isIOS ? () { // Only triggered on iOS showAlert('Screenshot attempted'); } : null, builder: (context, onInit, onDispose) => YourWidget(), ) // Android global protection if (Platform.isAndroid) { SecureContent().preventScreenshotAndroid(true); }
-
Performance: Only wrap widgets that need protection to maintain optimal performance
-
State Management: Use
isSecure
to dynamically toggle protection -
User Experience: Provide alternative feedback mechanisms for Android
-
Testing: Test protection in both debug and release modes on both platforms
Example
A complete example showing different use cases:
class SecureScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Secure Screen')),
body: Column(
children: [
// Regular content
Text('This content can be captured'),
// Secure content
SecureWidget(
isSecure: true,
onScreenshotCaptured: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Screenshots not allowed!')),
);
},
builder: (context, onInit, onDispose) => Container(
padding: EdgeInsets.all(16),
child: Text('This content is protected'),
),
),
],
),
);
}
}
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.