attachment_engine 0.0.1-dev.4 copy "attachment_engine: ^0.0.1-dev.4" to clipboard
attachment_engine: ^0.0.1-dev.4 copied to clipboard

Universal attachment engine for Flutter to resolve, cache, download, preview, and render documents, media, and web content using native platform APIs.

example/lib/main.dart

// Copyright 2026 DHC Tech
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.

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

import 'config_demo_page.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await AttachmentManager.initializeDefault();
  runApp(const AttachmentEngineExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Attachment Engine Example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      home: const AttachmentListPage(),
    );
  }
}

/// Sample attachments pointing at small, publicly-reachable network
/// resources so this example doubles as a manual real-flow validation of
/// the resolve -> cache -> download -> render pipeline.
final sampleAttachments = <Attachment>[
  Attachment(
    id: 'sample-image',
    name: 'Sample Image (network)',
    extension: 'jpg',
    mimeType: 'image/jpeg',
    remoteUrl:
        'https://upload.wikimedia.org/wikipedia/commons/a/a3/June_odd-eyed-cat.jpg',
    source: const AttachmentSource.url(
      'https://upload.wikimedia.org/wikipedia/commons/a/a3/June_odd-eyed-cat.jpg',
    ),
    attachmentType: AttachmentType.image,
  ),
  Attachment(
    id: 'sample-pdf',
    name: 'Sample PDF (network)',
    extension: 'pdf',
    mimeType: 'application/pdf',
    remoteUrl:
        'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
    source: const AttachmentSource.url(
      'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
    ),
    attachmentType: AttachmentType.pdf,
  ),
  Attachment(
    id: 'sample-video',
    name: 'Sample MP4 (network)',
    extension: 'mp4',
    mimeType: 'video/mp4',
    remoteUrl:
        'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
    source: const AttachmentSource.url(
      'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
    ),
    attachmentType: AttachmentType.video,
  ),
  Attachment(
    id: 'sample-audio',
    name: 'Sample MP3 (network)',
    extension: 'mp3',
    mimeType: 'audio/mpeg',
    remoteUrl: 'https://download.samplelib.com/mp3/sample-9s.mp3',
    source: const AttachmentSource.url(
      'https://download.samplelib.com/mp3/sample-9s.mp3',
    ),
    attachmentType: AttachmentType.audio,
  ),
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Attachment Engine Example'),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            tooltip: 'AttachmentEngineConfig demo',
            onPressed: () {
              Navigator.of(
                context,
              ).push(MaterialPageRoute(builder: (_) => const ConfigDemoPage()));
            },
          ),
        ],
      ),
      body: AttachmentList(
        attachments: sampleAttachments,
        onTapAttachment: (attachment) {
          Navigator.of(context).push(
            MaterialPageRoute(
              builder: (_) => AttachmentDetailPage(attachment: attachment),
            ),
          );
        },
      ),
    );
  }
}

/// Resolves the tapped attachment (download/cache as needed) and then
/// shows it via [AttachmentViewer], demonstrating the full pipeline.
class AttachmentDetailPage extends StatefulWidget {
  const AttachmentDetailPage({super.key, required this.attachment});

  final Attachment attachment;

  @override
  State<AttachmentDetailPage> createState() => _AttachmentDetailPageState();
}

class _AttachmentDetailPageState extends State<AttachmentDetailPage> {
  late Future<ResolvedAttachment> _future;

  @override
  void initState() {
    super.initState();
    _future = AttachmentManager.instance.open(widget.attachment);
  }

  void _retry() {
    setState(() {
      _future = AttachmentManager.instance.retry(widget.attachment);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.attachment.name)),
      body: FutureBuilder<ResolvedAttachment>(
        future: _future,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            final error = snapshot.error;
            final failure = error is AttachmentResolutionException
                ? error.failure
                : const UnknownFailure();
            return AttachmentErrorView(failure: failure, onRetry: _retry);
          }
          final resolved = snapshot.data!;
          return AttachmentViewer(
            attachment: resolved.attachment,
            registry: AttachmentManager.instance.rendererRegistry,
          );
        },
      ),
    );
  }
}