After adding path and path_provider to our project, we are now ready to write our first file to the app. Like we did with SharedPreferences and FlutterSecureStorage, we’ll begin by creating a helper file that will deal with the reading and writing actions for our files.
So in the lib folder of your project, create a new file, and call it file_helper.dart.
Here let’s create a new class, called FileHelper.
Again, let’s use the factory pattern here. I’ll just paste the code here, but if you want a refresher on how this works, have a look at the SharedPreferences lesson (at the lesson called “CRUD with SharedPreferences” in this course).
The first method we want to add here is a getter that returns the path where our files will be saved:
In general, all tasks that deal with the file system are asynchronous, so this is a Future, of type string. As we want to create a getter, let’s add the get keyword, and let’s call this: _path, with an underscore, and mark it as async. As you might remember, a getter is a method that provides read only access to object properties.
This is where we’ll use the path_provider package: let’s create a final called directory, that awaits the getApplicationDocumentsDirectory method: this retrieves the local documents directory for your app; the getApplicationDocumentsDirectory method returns a future of a directory object. Among its properties, there’s the path string, that contains the path of the documents directory parsed as a string. And that’s what we are returning here.
Next return the directory path.
Future<String> get _path async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Now that we have the path, let’s create a method that returns the file at the specified location: it will return a Future, of type File, and tale a fileName as its parameter. As usual let’s mark it as async.
Inside the method, let’s await for the path of the documents directory, then return a new File object, whos path is p.join, passing the path and the filename.
So the join method, from the path plugin, will build the full name of the file, including its path, and write it correctly for the system you are using: so it will add a single back slash, a slash, or a double backs slash based on the system where the app is running on, automatically.
Future<File> getFile(String fileName) async {
final path = await _path;
return File(p.join(path, fileName));
}
Now we want a method to write into the file some text content:
This is a future, of type file, and we can call it writeFile. It takes the name of the file, which is a string, and the content we want to write into this file, again a string. It’s async.
Inside the method, let’s retrieve the file: it’s final, localFile, and for it’s value let’s await the result of the getFile method, passing fileName.
Finally let’s return localFile.writeAsString and pass the content. Here writeAsString will write into the file the text that was passed (a string name content in this case).
Future<File> writeFile(String fileName, String content) async {
final localFile = await getFile(fileName);
return localFile.writeAsString(content);
}
Let’s also create a method to read the file content: it’s a Future, of type String, and we can call this readFile. It takes the filename as a string, and it’s async. In the method, let’s write a try/catch block to avoid errors if the file is not found or cannot be opened. In the try block, let0s create a final called localFile, that awaits the getFile method, passing the fileName.
Here let’s return await localfile, and on that let’s call the readAsString method.
In case there is an error, write to the console with the print method: ‘Error reading file: $e’); And then just return an empty string.
Future<String> readFile(String fileName) async {
try {
final localFile = await getFile(fileName);
return await localFile.readAsString();
} catch (e) {
print('Error reading file: $e');
return '';
}
}
This completes the FileHelper. Next, let’s connect it to the user interface!