flutter_file_saver 0.10.0 copy "flutter_file_saver: ^0.10.0" to clipboard
flutter_file_saver: ^0.10.0 copied to clipboard

Interface to provide a way to save files on the device in Flutter.

flutter_file_saver #

melos Pub Version

Interface to provide a way to save files on the device in Flutter.

Platform Support #

Android iOS Web Windows Linux MacOS
writeFileAsBytes ❌️ ❌️
writeFileAsString ❌️ ❌️

Under the hood, each implementation tries to use the native dialog to save the file:

Getting Started #

Import the package #

dependencies:
    flutter_file_saver: any

Platform Setup #

Android Setup
android {
    defaultConfig {
        minSdkVersion 19
    }
}

Check example

iOS Setup (Needs iOS 16+)

Add the following permissions to your ios/Runner/Info.plist:

<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>

Check example

MacOS Setup

Add the following permissions to your macos/Runner/DebugProfile.entitlements:

<key>com.apple.security.files.user-selected.read-write</key>
<true/>

Check example

Methods #

writeFileAsBytes #

Write a file on the device from a Uint8List.

FlutterFileSaver().writeFileAsBytes(
    fileName: 'file.txt',
    bytes: fileBytes,
);

writeFileAsString #

Write a file on the device from a String. This will most of the time convert your data and perform a call to writeFileAsBytes.

FlutterFileSaver().writeFileAsString(
    fileName: 'file.txt',
    string: 'Hello World!',
);

Known Issues #

Web - SecurityError: Failed to execute 'showSaveFilePicker' on 'Window' #

This is a known issue with the showSaveFilePicker JavaScript API on the Web. It is caused when writeFileAsBytes/writeFileAsString is not immediately called after a user interaction (like a button click). This is a security feature of the browser to prevent malicious sites from opening file pickers without user consent.

ElevatedButton(
    onPressed: () async {
        final bytes = await compressFile();

        // Might throw a SecurityError if compressFile() has taken too long to execute.
        FlutterFileSaver().writeFileAsBytes(bytes: bytes, fileName: myFileName);
    },
    child: const Text('Save File'),
),

The fix is to call writeFileAsBytes/writeFileAsString immediately after a user interaction. For example, by having a two-phase save flow with a button specifically made to save the file:

ElevatedButton(
    onPressed: () async {
        // 1st phase: compress the file
        final bytes = await compressFile();

        if (!context.mounted) return;
        // 2nd phase: show a dialog to save the file
        showDialog(context: context, builder: (_) => DownloadDialog(bytes: bytes, fileName: myFileName));
    },
    child: const Text('Save File'),
),


class DownloadDialog extends StatelessWidget {
    const DownloadDialog({super.key, required this.bytes, required this.fileName});

    final Uint8List bytes;
    final String fileName;

    @override
    Widget build(BuildContext context) {
        return AlertDialog(
            title: const Text('Save File'),
            content: const Text('Do you want to save the file?'),
            actions: [
                TextButton(
                    onPressed: () {
                        // Will work because it is called immediately after a user interaction.
                        FlutterFileSaver().writeFileAsBytes(bytes: bytes, fileName: fileName);
                        Navigator.pop(context);
                    },
                    child: const Text('Save'),
                ),
            ],
        );
    }
}

Check the example