no_screenshot

no_screenshot Pub Star on Github Flutter Website

A Flutter plugin to disable screenshots, block screen recording, detect screenshot events, and show a custom image overlay in the app switcher on Android, iOS, and macOS.

Features

Feature Android iOS macOS
Disable screenshot & screen recording
Enable screenshot & screen recording
Toggle screenshot protection
Listen for screenshot events (stream)
Image overlay in app switcher / recents -

Note: State is automatically persisted via native SharedPreferences / UserDefaults. You do not need to track didChangeAppLifecycleState.

Installation

Add no_screenshot to your pubspec.yaml:

dependencies:
  no_screenshot: ^0.3.3-beta.1

Then run:

flutter pub get

Quick Start

import 'package:no_screenshot/no_screenshot.dart';

final noScreenshot = NoScreenshot.instance;

// Disable screenshots & screen recording
await noScreenshot.screenshotOff();

// Re-enable screenshots & screen recording
await noScreenshot.screenshotOn();

// Toggle between enabled / disabled
await noScreenshot.toggleScreenshot();

Usage

1. Screenshot & Screen Recording Protection

Block or allow screenshots and screen recording with a single method call.

final _noScreenshot = NoScreenshot.instance;

// Disable screenshots (returns true on success)
Future<void> disableScreenshot() async {
  final result = await _noScreenshot.screenshotOff();
  debugPrint('screenshotOff: $result');
}

// Enable screenshots (returns true on success)
Future<void> enableScreenshot() async {
  final result = await _noScreenshot.screenshotOn();
  debugPrint('screenshotOn: $result');
}

// Toggle the current state
Future<void> toggleScreenshot() async {
  final result = await _noScreenshot.toggleScreenshot();
  debugPrint('toggleScreenshot: $result');
}

2. Screenshot Monitoring (Stream)

Listen for screenshot events in real time. Monitoring is off by default -- you must explicitly start it.

final _noScreenshot = NoScreenshot.instance;

// 1. Subscribe to the stream
_noScreenshot.screenshotStream.listen((snapshot) {
  debugPrint('Protection active: ${snapshot.isScreenshotProtectionOn}');
  debugPrint('Screenshot taken: ${snapshot.wasScreenshotTaken}');
  debugPrint('Path: ${snapshot.screenshotPath}');
});

// 2. Start monitoring
await _noScreenshot.startScreenshotListening();

// 3. Stop monitoring when no longer needed
await _noScreenshot.stopScreenshotListening();

The stream emits a ScreenshotSnapshot object:

Property Type Description
isScreenshotProtectionOn bool Whether screenshot protection is currently active
wasScreenshotTaken bool Whether a screenshot was just captured
screenshotPath String Path of the captured screenshot (when available)

3. Image Overlay (App Switcher / Recents)

Show a custom image when the app appears in the app switcher or recents screen. This prevents sensitive content from being visible in thumbnails.

final _noScreenshot = NoScreenshot.instance;

// Toggle the image overlay on/off (returns the new state)
Future<void> toggleOverlay() async {
  final isActive = await _noScreenshot.toggleScreenshotWithImage();
  debugPrint('Image overlay active: $isActive');
}

Setup: Place your overlay image in the platform-specific asset locations:

  • Android: android/app/src/main/res/drawable/image.png
  • iOS: Add an image named image to your asset catalog (Runner/Assets.xcassets/image.imageset/)

When enabled, the overlay image is shown whenever the app goes to the background or appears in the app switcher. Screenshot protection is also automatically activated.

Full Example

Below is a complete example showing all features together. See the full source in example/lib/main.dart.

import 'package:flutter/material.dart';
import 'package:no_screenshot/no_screenshot.dart';
import 'package:no_screenshot/screenshot_snapshot.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'No Screenshot Example',
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _noScreenshot = NoScreenshot.instance;
  bool _isMonitoring = false;
  bool _isOverlayImageOn = false;
  ScreenshotSnapshot _latestSnapshot = ScreenshotSnapshot(
    isScreenshotProtectionOn: false,
    wasScreenshotTaken: false,
    screenshotPath: '',
  );

  @override
  void initState() {
    super.initState();
    _noScreenshot.screenshotStream.listen((value) {
      setState(() => _latestSnapshot = value);
      if (value.wasScreenshotTaken) {
        debugPrint('Screenshot taken at path: ${value.screenshotPath}');
      }
    });
  }

  // ── Screenshot Protection ──────────────────────────────────────────

  Future<void> _disableScreenshot() async {
    final result = await _noScreenshot.screenshotOff();
    debugPrint('screenshotOff: $result');
  }

  Future<void> _enableScreenshot() async {
    final result = await _noScreenshot.screenshotOn();
    debugPrint('screenshotOn: $result');
  }

  Future<void> _toggleScreenshot() async {
    final result = await _noScreenshot.toggleScreenshot();
    debugPrint('toggleScreenshot: $result');
  }

  // ── Screenshot Monitoring ──────────────────────────────────────────

  Future<void> _startMonitoring() async {
    await _noScreenshot.startScreenshotListening();
    setState(() => _isMonitoring = true);
  }

  Future<void> _stopMonitoring() async {
    await _noScreenshot.stopScreenshotListening();
    setState(() => _isMonitoring = false);
  }

  // ── Image Overlay ─────────────────────────────────────────────────

  Future<void> _toggleScreenshotWithImage() async {
    final result = await _noScreenshot.toggleScreenshotWithImage();
    debugPrint('toggleScreenshotWithImage: $result');
    setState(() => _isOverlayImageOn = result);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('No Screenshot Example')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Screenshot protection buttons
          ElevatedButton(
            onPressed: _disableScreenshot,
            child: const Text('Disable Screenshot'),
          ),
          ElevatedButton(
            onPressed: _enableScreenshot,
            child: const Text('Enable Screenshot'),
          ),
          ElevatedButton(
            onPressed: _toggleScreenshot,
            child: const Text('Toggle Screenshot'),
          ),

          const Divider(),

          // Monitoring buttons
          ElevatedButton(
            onPressed: _startMonitoring,
            child: const Text('Start Monitoring'),
          ),
          ElevatedButton(
            onPressed: _stopMonitoring,
            child: const Text('Stop Monitoring'),
          ),
          Text('Monitoring: $_isMonitoring'),
          Text('Last snapshot: $_latestSnapshot'),

          const Divider(),

          // Image overlay toggle
          ElevatedButton(
            onPressed: _toggleScreenshotWithImage,
            child: const Text('Toggle Image Overlay'),
          ),
          Text('Overlay active: $_isOverlayImageOn'),
        ],
      ),
    );
  }
}

API Reference

Method Return Type Description
NoScreenshot.instance NoScreenshot Singleton instance of the plugin
screenshotOff() Future<bool> Disable screenshots & screen recording
screenshotOn() Future<bool> Enable screenshots & screen recording
toggleScreenshot() Future<bool> Toggle screenshot protection on/off
toggleScreenshotWithImage() Future<bool> Toggle image overlay mode (returns new state)
startScreenshotListening() Future<void> Start monitoring for screenshot events
stopScreenshotListening() Future<void> Stop monitoring for screenshot events
screenshotStream Stream<ScreenshotSnapshot> Stream of screenshot activity events

Contributors

Thanks to everyone who has contributed to this project!

@fonkamloic @zhangyuanyuan-bear @qk7b @T-moz @ggiordan @Musaddiq625 @albertocappellina-intesys @kefeh

License

BSD 3-Clause License. See LICENSE for details.