screenshot_shield

Detects user screenshots and optionally prevents screen capture on Android and iOS.

Platform behaviour

Screenshot detection is best-effort and platform-specific:

Capability Android iOS
Screenshot detection Yes - Android 14+ uses the system DETECT_SCREEN_CAPTURE API; older versions watch the media store and report shortly after a screenshot is saved Yes - reports immediately via the UIApplicationUserDidTakeScreenshotNotification system notification
Prevent screen capture (setProtection(preventCapture: true)) Yes - adds the secure window flag so the captured frame is blank Yes - a hidden secure text field makes the system exclude the window from snapshots, so screenshots come out blank
Screenshot events while protected No - the secure window flag blanks the frame (it is never saved, so the media-store observer never fires) and, on Android 14+, the system withholds the capture callback for secure windows. The guards resolve this by dropping prevention when detection is also requested, so the event fires and the guarded screen is re-rasterized into a shareable image Yes - the detection notification still fires, and the guarded screen can still be re-rasterized into a shareable image
Runtime permission DETECT_SCREEN_CAPTURE (auto-granted, Android 14+ only); on Android 9 (API 28) and below, detection reads the media store and needs READ_EXTERNAL_STORAGE, which the host app must request at runtime Not required

On Android, preventCapture (the secure window flag) and screenshot detection are mutually exclusive: the blanked frame is never saved and the system withholds the capture callback for secure windows, so no event fires while prevention is on. The guards handle this automatically by dropping prevention when detectScreenshots is also enabled on Android. To keep the blanked frame instead, set forcePreventCapture: true on the guard (accepting that onScreenshotDetected will not fire). On Android 14+, the system shows a notice whenever the screenshot detection API fires. Screenshots taken via ADB or instrumentation tests are not detected by either path.

Desktop (Windows, Linux)

The package registers on Windows and Linux so the widget layer works there, but desktop has no OS screenshot-detection or screenshot-prevention APIs, so onScreenshotDetected never fires, startListening is a no-op, and preventCapture cannot blank the capture. What does apply:

  • On Windows, enabling setProtection(backgroundBlur: true) cloaks the window when it is deactivated or minimized, hiding it from alt-tab and the taskbar preview.
  • On Linux, detection and protection are unavailable (no standard mechanism); the Dart widgets still work.

Usage

Provide a ScreenshotShield to the tree with ScreenshotShieldScope, then wrap the screen you want to guard with a ScreenshotShieldRouteGuard. The guard observes the route it lives on: protection and screenshot listening are enabled while the route is in view and released automatically when another route covers it.

final RouteObserver<ModalRoute<void>> routeObserver = RouteObserver<ModalRoute<void>>();

// Wrap your app with the scope and register the observer with the navigator:
ScreenshotShieldScope(
  shield: ScreenshotShield(),
  routeObserver: routeObserver,
  child: MaterialApp(
    navigatorObservers: [routeObserver],
    home: const HomeScreen(),
  ),
);

// Inside a guarded screen:
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return ScreenshotShieldRouteGuard(
      onScreenshotDetected: (image) {
        // `image` is a PNG of the guarded screen (null if capture failed).
        // Present it to the user, e.g. via `share_plus`.
      },
      child: const Scaffold(
        body: Center(child: Text('Guarded')),
      ),
    );
  }
}

The guard reads its ScreenshotShield from the nearest ScreenshotShieldScope with ScreenshotShieldScope.of(context). Configure the guard with a preventCapture flag (Android only, default true), a detectScreenshots flag (default true), a forcePreventCapture flag (default false) that makes blanking win over detection on Android, a captureOnScreenshot flag (default true), and an optional onScreenshotDetected callback. With captureOnScreenshot the guarded subtree is re-rasterized into a PNG on each screenshot, so the app can show exactly what was on screen even when the OS frame is blanked or unavailable.

For screens that are not managed by a Navigator (custom tabs, embedded views, overlays), use ScreenshotShieldGuard instead of the route guard. It provides the same flags and callback but activates while the widget is mounted and its active flag is true, without needing a RouteObserver.

Detect-and-notify mode

By default preventCapture blanks the captured frame on Android and iOS, so the user sees a black screenshot. To follow a Snapchat-style flow instead - let the screenshot succeed and react in onScreenshotDetected (for example by sending the captured image or notifying a peer) - set preventCapture: false.

Background privacy

Screenshot detection only runs while the app is in the foreground, so a user in the background or the app switcher can take screenshots freely. To hide the app's content in the app switcher, enable the native background blur:

final shield = ScreenshotShield();
await shield.setProtection(backgroundBlur: true);

On iOS the key window is covered with a UIVisualEffectView blur when the app enters the background. On Android 12+ the window is blurred with RenderEffect; on older Android versions a dim overlay is shown because no public blur API exists. The feature is disabled by default.

Note that if preventCapture (FLAG_SECURE) is also enabled, the app-switcher snapshot stays blank and wins over the blur.

For lower-level control you can drive ScreenshotShield directly:

final shield = ScreenshotShield();
shield.onScreenshotDetected.listen((_) {
  // Show your own shareable image here.
});

// While this screen is visible:
await shield.startListening();
await shield.setProtection(preventCapture: true); // Blanks the captured frame.

// When leaving the screen:
await shield.stopListening();

When a screenshot is detected, present the user with your own shareable image (e.g. via share_plus) instead of the captured frame. Note that on Android preventCapture and screenshot detection cannot both be enabled: the secure window flag blanks the frame (never saved) and the system withholds the capture callback for secure windows, so no event fires while prevention is on. To get detection events on Android, keep preventCapture disabled; to blank the frame, accept that no events will fire. On iOS the screenshot is blanked and the detection event still fires, and the guarded screen can be re-rasterized into a shareable image.

iOS configuration

The iOS implementation observes UIApplicationUserDidTakeScreenshotNotification and requires no permissions or Info.plist entries. Screenshot detection fires while the app is in the foreground; screenshots taken while the app is backgrounded (e.g. from the app switcher) are not reported.

Example app

A runnable example lives in example/. It demonstrates detection, the captured-image callback, and the background blur, with toggles for each feature:

cd example
flutter run

Install

Add the dependency to your pubspec.yaml:

dependencies:
  screenshot_shield: ^0.1.0