Save any file in external storage in Flutter app: Full Guide

Fri, May 26, 2023

Read in 4 minutes

In this tutorial we will learn how to save files on both Android and IOS devices in Flutter app so that user can access saved files on their device.

First, let us add neccessary packages in our pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
    
  permission_handler: <latest version>
  share_plus: <latest version>
  path_provider: <latest version>

* We need share_plus package for only IOS because on iOS, the app’s document directory is sandboxed and not directly accessible to the user or other apps. We want the user to be able to see the saved file.

Android: Now let us add permissions inside android/app/src/main/AndroidManifest.xml file, right above the application tag:

    
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
 <application ...

Then we need to ask user’s permission to write files on external storage. So let us create permission_helper.dart file and paste the following code inside it. (I will explain the code below in detail below.)

import 'dart:developer';

import 'package:permission_handler/permission_handler.dart';

class PermissionHelper {
  static Future<bool> requestStoragePermissions() async {
    var status = await Permission.storage.status;
    log("=> storage permission satus: $status");
    if (!status.isGranted) {
      status = await Permission.storage.request();
    }

    return status == PermissionStatus.granted;
  }
}
  1. The dart:developer package is imported, which provides logging functionalities.
  2. The permission_handler package is imported, which is used for handling permission requests.
  3. The PermissionHelper class is defined.
  4. The requestStoragePermissions method is a static method that returns a Future<bool>.
  5. Inside the method, the current status of the storage permission is obtained using Permission.storage.status.
  6. The status is logged using log function from dart:developer.
  7. If the permission is not granted (status.isGranted is false), a permission request is made using Permission.storage.request().
  8. The updated status after the request is assigned back to the status variable.
  9. The method returns true if the permission status is PermissionStatus.granted, indicating that the permission was granted.
  10. If the permission was not granted, the method returns false.

This code is essentially requesting and checking the status of storage permissions using the permission_handler package and logging the permission status using dart:developer.

Now let us create a file_downloader_helper.dart file and past the following code into it. (I will explain the code in detail below.)

import 'dart:developer';
import 'dart:io';

import 'package:fluttertoast/fluttertoast.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';

class FileDownloaderHelper {
  static Future<void> saveFileOnDevice(String fileName, File inFile) async {
    try {
      if (Platform.isAndroid) {
        // Check if the platform is Android
        final directory = Directory("/storage/emulated/0/Download");

        if (!directory.existsSync()) {
          // Create the directory if it doesn't exist
          await directory.create();
        }
        final path = '${directory.path}/$fileName';
        final bytes = await inFile.readAsBytes();
        final outFile = File(path);

        final res = await outFile.writeAsBytes(bytes, flush: true);
        log("=> saved file: ${res.path}");
      } else {
        // IOS
        final directory = await getApplicationDocumentsDirectory();
        // Get the application documents directory path
        final path = '${directory.path}/$fileName';
        final bytes = await inFile.readAsBytes();
        final res = await Share.shareXFiles([XFile(path, bytes: bytes)]);
        log("=> saved status: ${res.status}");
      }
    } catch (e) {
      throw Exception(e);
    }
  }
}

Explanation:

  1. The FileDownloaderHelper class encapsulates the functionality for saving a file on the device.

  2. The saveFileOnDevice method is a static method that takes two parameters: fileName (the name of the file) and inFile (the input file to be saved).

  3. The method begins by checking the current platform using Platform.isAndroid. If it is Android, the code block inside the if statement will be executed. Otherwise, the code block inside the else statement will be executed (assumed to be iOS).

  4. For Android:

    • It creates a Directory object representing the “/storage/emulated/0/Download” directory.
    • If the directory doesn’t exist, it creates it using directory.create().
    • It constructs the full file path by concatenating the directory path and the fileName.
    • It reads the bytes from the inFile using inFile.readAsBytes().
    • It creates a new File object with the constructed path.
    • It writes the bytes to the file using outFile.writeAsBytes(), with flush set to true to ensure the data is immediately written to the file system.
    • The resulting File object is logged using log().
  5. For iOS:

    • It retrieves the application documents directory path using getApplicationDocumentsDirectory().
    • It constructs the full file path by concatenating the directory path and the fileName.
    • It reads the bytes from the inFile using inFile.readAsBytes().
    • It shares the file using Share.shareXFiles() by passing a list containing an XFile object representing the file path and the file’s bytes.
    • The resulting status of the sharing operation is logged using log().
  6. If any exception occurs during the process, it is caught and re-thrown as an Exception.

This code allows you to save a file on the device based on the platform (Android or iOS) and log the relevant information or handle exceptions accordingly.

To use the code you can call the functions like below:

void saveMyFile() async {
    final granted = await PermissionHelper.requestStoragePermissions();
    if (!granted) return;
    FileDownloaderHelper.saveFileOnDevice("sample.pdf",File("path/to/your/file/sample.pdf"));
}

Shohruh AK





See Also

Gradient Text Effect in Flutter Without Package
Flutter Login Screen UI tutorial
How to create animated list view in Flutter without any package: AnimatedList widget
Flutter: Copy ZIP folder with all subfolders from assets to application documents directory
How to create animated loading button in Flutter