Flutter Lesson 51 of 83 4 min read
Reading and Writing Files in Flutter with path_provider
Read and write files in a Flutter app: find the right folder with path_provider, save text and JSON, and store downloaded images.
On this page
For data that is too large or too structured for preferences, but does not need a database, write a file. An app cannot write wherever it likes. Each platform gives it private folders, and the path_provider package finds them.
flutter pub add path_provider
The file operations themselves come from dart:io, covered in the Dart chapter on files. dart:io does not work in a web build.
The folders #
| Function | Use for | Cleared by the system? |
|---|---|---|
getApplicationDocumentsDirectory() | Files the user created and would miss: notes, exports | No |
getApplicationSupportDirectory() | Files the app needs but the user does not see: databases, settings | No |
getTemporaryDirectory() | Caches and scratch files that can be recreated | Yes, at any time |
getApplicationCacheDirectory() | Larger caches | Yes |
Reading and writing text #
import 'dart:io';
import 'package:path_provider/path_provider.dart';
Future<File> _notesFile() async {
final dir = await getApplicationDocumentsDirectory();
return File('${dir.path}/notes.txt');
}
Future<void> saveNotes(String text) async {
final file = await _notesFile();
await file.writeAsString(text);
}
Future<String> loadNotes() async {
try {
final file = await _notesFile();
return await file.readAsString();
} on PathNotFoundException {
return ''; // first run: nothing saved yet
}
}
Always handle the case where the file does not exist yet.
A notes screen #
import 'package:flutter/material.dart';
class NotesPage extends StatefulWidget {
const NotesPage({super.key});
@override
State<NotesPage> createState() => _NotesPageState();
}
class _NotesPageState extends State<NotesPage> {
final _controller = TextEditingController();
@override
void initState() {
super.initState();
loadNotes().then((text) {
if (mounted) _controller.text = text;
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _save() async {
await saveNotes(_controller.text);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Saved')));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Notes'),
actions: [IconButton(onPressed: _save, icon: const Icon(Icons.save))],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: TextField(
controller: _controller,
maxLines: null,
expands: true,
decoration: const InputDecoration(
hintText: 'Write something...',
border: InputBorder.none,
),
),
),
);
}
}
Saving a list as JSON #
import 'dart:convert';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class TodoStore {
Future<File> get _file async {
final dir = await getApplicationSupportDirectory();
return File('${dir.path}/todos.json');
}
Future<List<Todo>> load() async {
try {
final text = await (await _file).readAsString();
final list = jsonDecode(text) as List<dynamic>;
return [for (final item in list) Todo.fromJson(item as Map<String, dynamic>)];
} on PathNotFoundException {
return [];
} on FormatException {
return []; // the file is damaged: start again
}
}
Future<void> save(List<Todo> todos) async {
final file = await _file;
final temp = File('${file.path}.tmp');
await temp.writeAsString(jsonEncode([for (final t in todos) t.toJson()]));
await temp.rename(file.path); // replace in one step, so a crash cannot leave half a file
}
}
This is a perfectly good store for a few hundred items. Every save rewrites the whole file, so for thousands of records, or for searching and sorting, use SQLite.
Downloading and saving a file #
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
Future<File> downloadImage(String url, String fileName) async {
final response = await http.get(Uri.parse(url));
if (response.statusCode != 200) {
throw Exception('Download failed');
}
final dir = await getTemporaryDirectory();
final file = File('${dir.path}/$fileName');
return file.writeAsBytes(response.bodyBytes);
}
// Display it
Image.file(file)
Building paths #
Add the path package to join paths correctly on every platform.
import 'package:path/path.dart' as p;
final file = File(p.join(dir.path, 'exports', 'report.csv'));
await file.parent.create(recursive: true); // make sure the folder exists
Letting the user see or share a file #
The app’s folders are private. Other apps and the user’s file manager cannot see them. To hand a file over:
| Goal | Package |
|---|---|
| Share through another app | share_plus |
| Let the user choose where to save, or pick a file to open | file_picker |
| Save a photo to the gallery | A gallery saver package |
On the web #
There is no file system. Use shared_preferences (which uses the browser’s local storage), IndexedDB through a package such as hive or drift, or trigger a download.
Try it yourself #
Build a small journal app. Each entry has a date and text. Store all entries as a JSON file in the support directory, load them at start-up, and save after every change. Add an export button that writes the entries to a readable text file in the documents directory.