filesaverplus 0.0.5 copy "filesaverplus: ^0.0.5" to clipboard
filesaverplus: ^0.0.5 copied to clipboard

FileSaverPlus is a Flutter plugin for saving files to device storage with cross-platform support for Android, iOS, Web, Windows, Linux, and macOS.

example/lib/main.dart

import 'dart:convert';

import 'package:filesaverplus/filesaverplus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FileSaverPlus Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const DemoPage(),
    );
  }
}

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

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  final _fileSaver = FileSaverPlus();
  final List<_LogEntry> _log = [];
  bool _busy = false;

  // ── helpers ────────────────────────────────────────────────────────────────

  Uint8List _utf8(String s) => Uint8List.fromList(utf8.encode(s));

  void _addLog(String message, {bool isError = false}) {
    setState(() => _log.insert(0, _LogEntry(message, isError: isError)));
  }

  Future<void> _run(Future<void> Function() action) async {
    if (_busy) return;
    setState(() => _busy = true);
    try {
      await action();
    } finally {
      setState(() => _busy = false);
    }
  }

  // ── example actions ────────────────────────────────────────────────────────

  /// 1. Save a single plain-text file.
  Future<void> _saveSingleText() => _run(() async {
    const content = 'Hello from FileSaverPlus!\n\nThis is a plain-text file.';
    final path = await _fileSaver.saveFile(
      _utf8(content),
      'hello.txt',
      'text/plain',
    );
    _addLog('✓ Saved text file\n  → $path');
  });

  /// 2. Save a single HTML file.
  Future<void> _saveSingleHtml() => _run(() async {
    const content = '''<!DOCTYPE html>
<html>
<head><title>FileSaverPlus</title></head>
<body>
  <h1>Hello, FileSaverPlus!</h1>
  <p>This HTML file was saved by the FileSaverPlus plugin.</p>
</body>
</html>''';
    final path = await _fileSaver.saveFile(
      _utf8(content),
      'demo.html',
      'text/html',
    );
    _addLog('✓ Saved HTML file\n  → $path');
  });

  /// 3. Save a JSON file.
  Future<void> _saveSingleJson() => _run(() async {
    final data = jsonEncode({
      'plugin': 'filesaverplus',
      'version': '0.0.5',
      'platforms': ['android', 'ios', 'web', 'windows', 'linux', 'macos'],
      'savedAt': DateTime.now().toIso8601String(),
    });
    final path = await _fileSaver.saveFile(
      _utf8(data),
      'plugin_info.json',
      'application/json',
    );
    _addLog('✓ Saved JSON file\n  → $path');
  });

  /// 4. Save multiple files in one call (HTML + TXT + JSON).
  Future<void> _saveMultiple() => _run(() async {
    final paths = await _fileSaver.saveMultipleFiles(
      dataList: [
        _utf8('<h1>Report</h1><p>Generated by FileSaverPlus.</p>'),
        _utf8('Summary\n-------\nMultiple files saved in one call.'),
        _utf8(jsonEncode({'status': 'ok', 'count': 3})),
      ],
      fileNameList: ['report.html', 'summary.txt', 'result.json'],
      mimeTypeList: ['text/html', 'text/plain', 'application/json'],
    );
    _addLog('✓ Saved ${paths.length} files');
    for (final p in paths) {
      _addLog('  → $p');
    }
  });

  /// 5. Duplicate-name handling — plugin auto-appends _2, _3, …
  Future<void> _saveDuplicates() => _run(() async {
    final paths = await _fileSaver.saveMultipleFiles(
      dataList: [
        _utf8('File A content'),
        _utf8('File B content'),
        _utf8('File C content'),
      ],
      fileNameList: ['data.txt', 'data.txt', 'data.txt'],
      mimeTypeList: ['text/plain', 'text/plain', 'text/plain'],
    );
    _addLog('✓ Duplicate names auto-renamed:');
    for (final p in paths) {
      _addLog('  → $p');
    }
  });

  /// 6. Show platform info and battery level.
  Future<void> _showDeviceInfo() => _run(() async {
    final version = await _fileSaver.platformVersion;
    final battery = await _fileSaver.batteryPercentage;
    _addLog(
      '✓ Platform: $version\n'
      '  Battery: ${battery != null ? '$battery%' : 'not available'}',
    );
  });

  /// 7. Error demo — passes empty data to trigger an ArgumentError.
  Future<void> _triggerError() => _run(() async {
    try {
      await _fileSaver.saveFile(
        Uint8List(0), // intentionally empty — triggers ArgumentError
        'empty.txt',
        'text/plain',
      );
    } on ArgumentError catch (e) {
      _addLog('✗ ArgumentError (expected): $e', isError: true);
    } on PlatformException catch (e) {
      _addLog('✗ PlatformException [${e.code}]: ${e.message}', isError: true);
    }
  });

  // ── UI ─────────────────────────────────────────────────────────────────────

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FileSaverPlus Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          if (_log.isNotEmpty)
            IconButton(
              icon: const Icon(Icons.delete_outline),
              tooltip: 'Clear log',
              onPressed: () => setState(() => _log.clear()),
            ),
        ],
      ),
      body: Column(
        children: [
          // ── action list ─────────────────────────────────────────────────
          Expanded(
            flex: 2,
            child: ListView(
              padding: const EdgeInsets.all(12),
              children: [
                _SectionHeader('Single file'),
                _ActionTile(
                  icon: Icons.text_snippet_outlined,
                  title: 'Save text file',
                  subtitle: 'hello.txt — text/plain',
                  onTap: _busy ? null : _saveSingleText,
                ),
                _ActionTile(
                  icon: Icons.html_outlined,
                  title: 'Save HTML file',
                  subtitle: 'demo.html — text/html',
                  onTap: _busy ? null : _saveSingleHtml,
                ),
                _ActionTile(
                  icon: Icons.data_object_outlined,
                  title: 'Save JSON file',
                  subtitle: 'plugin_info.json — application/json',
                  onTap: _busy ? null : _saveSingleJson,
                ),
                const SizedBox(height: 8),
                _SectionHeader('Multiple files'),
                _ActionTile(
                  icon: Icons.file_copy_outlined,
                  title: 'Save 3 files at once',
                  subtitle: 'report.html + summary.txt + result.json',
                  onTap: _busy ? null : _saveMultiple,
                ),
                _ActionTile(
                  icon: Icons.drive_file_rename_outline,
                  title: 'Duplicate name handling',
                  subtitle: 'Three "data.txt" → auto-renamed to _2, _3',
                  onTap: _busy ? null : _saveDuplicates,
                ),
                const SizedBox(height: 8),
                _SectionHeader('Device info'),
                _ActionTile(
                  icon: Icons.info_outline,
                  title: 'Platform version & battery',
                  subtitle: 'getPlatformVersion · getBatteryPercentage',
                  onTap: _busy ? null : _showDeviceInfo,
                ),
                const SizedBox(height: 8),
                _SectionHeader('Error handling'),
                _ActionTile(
                  icon: Icons.error_outline,
                  title: 'Trigger ArgumentError',
                  subtitle: 'Passes empty data — shows caught error below',
                  onTap: _busy ? null : _triggerError,
                  isDestructive: true,
                ),
              ],
            ),
          ),

          const Divider(height: 1),

          // ── log panel ──────────────────────────────────────────────────
          Expanded(
            child:
                _log.isEmpty
                    ? const Center(
                      child: Text(
                        'Tap an action above — results appear here.',
                        style: TextStyle(color: Colors.grey),
                      ),
                    )
                    : ListView.separated(
                      padding: const EdgeInsets.all(12),
                      itemCount: _log.length,
                      separatorBuilder: (_, __) => const SizedBox(height: 4),
                      itemBuilder: (context, i) {
                        final entry = _log[i];
                        return Text(
                          entry.message,
                          style: TextStyle(
                            fontFamily: 'monospace',
                            fontSize: 12,
                            color:
                                entry.isError ? Colors.red : Colors.green[800],
                          ),
                        );
                      },
                    ),
          ),

          if (_busy)
            const LinearProgressIndicator()
          else
            const SizedBox(height: 4),
        ],
      ),
    );
  }
}

// ── small widgets ─────────────────────────────────────────────────────────────

class _SectionHeader extends StatelessWidget {
  const _SectionHeader(this.label);
  final String label;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 4),
      child: Text(
        label.toUpperCase(),
        style: TextStyle(
          fontSize: 11,
          fontWeight: FontWeight.bold,
          letterSpacing: 1.2,
          color: Theme.of(context).colorScheme.primary,
        ),
      ),
    );
  }
}

class _ActionTile extends StatelessWidget {
  const _ActionTile({
    required this.icon,
    required this.title,
    required this.subtitle,
    required this.onTap,
    this.isDestructive = false,
  });

  final IconData icon;
  final String title;
  final String subtitle;
  final VoidCallback? onTap;
  final bool isDestructive;

  @override
  Widget build(BuildContext context) {
    final color =
        isDestructive ? Colors.red : Theme.of(context).colorScheme.primary;

    return Card(
      margin: const EdgeInsets.symmetric(vertical: 3),
      child: ListTile(
        leading: Icon(icon, color: onTap == null ? Colors.grey : color),
        title: Text(
          title,
          style: TextStyle(
            fontWeight: FontWeight.w600,
            color: onTap == null ? Colors.grey : null,
          ),
        ),
        subtitle: Text(subtitle, style: const TextStyle(fontSize: 12)),
        trailing: Icon(
          Icons.chevron_right,
          color: onTap == null ? Colors.grey : null,
        ),
        onTap: onTap,
      ),
    );
  }
}

class _LogEntry {
  const _LogEntry(this.message, {this.isError = false});
  final String message;
  final bool isError;
}
1
likes
150
points
118
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

FileSaverPlus is a Flutter plugin for saving files to device storage with cross-platform support for Android, iOS, Web, Windows, Linux, and macOS.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, flutter_web_plugins, path_provider, plugin_platform_interface, web

More

Packages that depend on filesaverplus

Packages that implement filesaverplus