flutter_better_scanner 1.0.2 copy "flutter_better_scanner: ^1.0.2" to clipboard
flutter_better_scanner: ^1.0.2 copied to clipboard

Drop-in document scanner: live edge detection, auto capture, crop, enhance, annotate and multi-page export. Bring your own UI or use the built-in screens.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_better_scanner/flutter_better_scanner.dart';

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

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

  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'flutter_better_scanner',
        theme: ThemeData(
          colorSchemeSeed: const Color(0xFF3B82F6),
          useMaterial3: true,
        ),
        home: const HomePage(),
      );
}

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

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

class _HomePageState extends State<HomePage> {
  /// Paths of everything produced so far, newest last.
  final List<String> _results = [];
  String? _status;

  @override
  void initState() {
    super.initState();
    // Optional: spawn the CV worker isolate up front so the first capture is
    // not the one that pays for it.
    BetterScanner.warmUp();
  }

  void _show(String message) => setState(() => _status = message);

  void _add(String? path, String label) {
    if (path == null) {
      _show('$label: cancelled');
      return;
    }
    setState(() {
      _results.add(path);
      _status = '$label: $path';
    });
  }

  /// The full scanner: camera, live edge detection, auto capture, then the
  /// built-in review screen with every editor.
  Future<void> _scanBatch() async {
    final result = await BetterScanner.openScanner(
      context,
      config: const ScannerConfig(
        captureMode: CaptureMode.multiple,
        shutterMode: ShutterMode.auto,
        autoCrop: true,
        autoEnhance: true,
        exportFormat: ExportFormat.jpg,
      ),
    );
    if (result == null || result.isEmpty) {
      _show('Scan cancelled');
      return;
    }
    setState(() {
      _results.addAll(result.imagePaths);
      _status = '${result.pages.length} page(s) scanned';
    });
  }

  /// One page, one path back.
  Future<void> _scanSingle() async {
    _add(await BetterScanner.scanSingle(context), 'Single page');
  }

  /// The package's editors, run a-la-carte on an image the scanner never saw.
  /// Here the input is the newest result, but any file path works.
  Future<void> _cropLast() async {
    final path = _results.lastOrNull;
    if (path == null) return _show('Scan something first');
    _add(await ScannerEditor.crop(context, imagePath: path), 'Cropped');
  }

  Future<void> _enhanceLast() async {
    final path = _results.lastOrNull;
    if (path == null) return _show('Scan something first');
    _add(await ScannerEditor.enhance(context, imagePath: path), 'Enhanced');
  }

  /// Headless — no screen is shown at all.
  Future<void> _blackAndWhiteLast() async {
    final path = _results.lastOrNull;
    if (path == null) return _show('Scan something first');
    _add(
      await ScannerEditor.applyEnhancement(
        imagePath: path,
        enhancement: ScanEnhancement.blackWhite,
      ),
      'B&W',
    );
  }

  @override
  Widget build(BuildContext context) {
    final latest = _results.lastOrNull;
    return Scaffold(
      appBar: AppBar(title: const Text('flutter_better_scanner')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                FilledButton.icon(
                  onPressed: _scanBatch,
                  icon: const Icon(Icons.document_scanner_outlined),
                  label: const Text('Scan documents'),
                ),
                OutlinedButton.icon(
                  onPressed: _scanSingle,
                  icon: const Icon(Icons.filter_1_outlined),
                  label: const Text('Scan one page'),
                ),
                OutlinedButton.icon(
                  onPressed: _cropLast,
                  icon: const Icon(Icons.crop),
                  label: const Text('Crop last'),
                ),
                OutlinedButton.icon(
                  onPressed: _enhanceLast,
                  icon: const Icon(Icons.auto_fix_high_outlined),
                  label: const Text('Enhance last'),
                ),
                OutlinedButton.icon(
                  onPressed: _blackAndWhiteLast,
                  icon: const Icon(Icons.contrast),
                  label: const Text('B&W last (headless)'),
                ),
              ],
            ),
          ),
          if (_status != null)
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              child: Text(
                _status!,
                style: Theme.of(context).textTheme.bodySmall,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            ),
          const Divider(height: 24),
          Expanded(
            child: latest == null
                ? const Center(child: Text('Nothing scanned yet'))
                : Padding(
                    padding: const EdgeInsets.all(16),
                    child: InteractiveViewer(
                      child: Image.file(File(latest), fit: BoxFit.contain),
                    ),
                  ),
          ),
          if (_results.length > 1)
            SizedBox(
              height: 92,
              child: ListView.separated(
                scrollDirection: Axis.horizontal,
                padding: const EdgeInsets.symmetric(horizontal: 16),
                itemCount: _results.length,
                separatorBuilder: (_, _) => const SizedBox(width: 8),
                itemBuilder: (_, i) => ClipRRect(
                  borderRadius: BorderRadius.circular(6),
                  child: Image.file(File(_results[i]), width: 68, fit: BoxFit.cover),
                ),
              ),
            ),
          const SizedBox(height: 16),
        ],
      ),
    );
  }
}
1
likes
120
points
102
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Drop-in document scanner: live edge detection, auto capture, crop, enhance, annotate and multi-page export. Bring your own UI or use the built-in screens.

Repository (GitHub)
View/report issues

Topics

#scanner #camera #document #ocr #image

License

MIT (license)

Dependencies

camera, ffi, flutter, image, image_picker, path, path_provider, uuid

More

Packages that depend on flutter_better_scanner

Packages that implement flutter_better_scanner