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;
}
}
dart:developer
package is imported, which provides logging functionalities.permission_handler
package is imported, which is used for handling permission requests.PermissionHelper
class is defined.requestStoragePermissions
method is a static method that returns a Future<bool>
.Permission.storage.status
.log
function from dart:developer
.status.isGranted
is false
), a permission request is made using Permission.storage.request()
.status
variable.true
if the permission status is PermissionStatus.granted
, indicating that the permission was granted.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:
The FileDownloaderHelper
class encapsulates the functionality for saving a file on the device.
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).
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).
For Android:
Directory
object representing the “/storage/emulated/0/Download” directory.directory.create()
.fileName
.inFile
using inFile.readAsBytes()
.File
object with the constructed path.outFile.writeAsBytes()
, with flush
set to true
to ensure the data is immediately written to the file system.File
object is logged using log()
.For iOS:
getApplicationDocumentsDirectory()
.fileName
.inFile
using inFile.readAsBytes()
.Share.shareXFiles()
by passing a list containing an XFile
object representing the file path and the file’s bytes.log()
.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"));
}