File Handling in Dart
By Syncfusion®, Inc. · Last updated
How to read and write files and folders with dart:io, and how to save and load data as JSON with dart:convert, model classes and safe parsing.
What Is File Handling?
File handling = reading data from files and writing data to files on a disk.
- Variables live only while the program runs. When it ends, they are gone.
- Files stay after the program ends. Next time, you can read them again.
- In Dart, file tools come from the library
dart:io.
Simple lesson: files are the program's long-term memory.
A Real-World Example
Think of the class attendance register.
- Every morning, the teacher reads yesterday's page.
- She adds today's names at the bottom. She does not tear out old pages.
- At the end of the year, a new register is created, and old ones are kept in a cupboard (a folder).
- If the register is missing, she reports it, instead of giving up teaching.
Try each action on a pretend file:
Every Run box on this page has its own pretend disk, made fresh for each run. It already holds:
notes.txt,marks.csvand a read-onlylocked.txt- a folder
datawithstudents.jsonandconfig.json
On your computer, dart:io uses your real disk. dart:io does not work in web apps.
What You Will Learn
In this lesson, you will learn:
- Paths, and how to read and write files, sync and async.
- How to append, check file details and work with folders.
- How to handle missing files and permissions.
- How to encode and decode JSON.
- How to write model classes with
fromJsonandtoJson, and parse safely.
Paths
Path = the address of a file on the disk.
| Kind | Starts from | Example |
|---|---|---|
| Absolute path | the top of the disk | /home/student/notes.txt or C:\school\notes.txt |
| Relative path | the current folder of the program | notes.txt, data/students.json |
..means "the folder above".- Prefer
/in Dart code. It works on Windows too.
A relative path and its absolute form
import 'dart:io';
void main() {
var f = File('data/students.json');
print(f.path);
print(f.absolute.path);
print(Directory.current.path);
}
Simple lesson: a relative path starts from where the program runs, and an absolute path starts from the top of the disk.
Reading Files
Whole File, Lines, or Bytes
| Method | Gives |
|---|---|
readAsStringSync() | the whole file as one String |
readAsLinesSync() | a List<String>, one item per line |
readAsBytesSync() | a List<int> of raw bytes |
Reading the notes file
import 'dart:io';
void main() {
var notes = File('notes.txt');
var text = notes.readAsStringSync();
print(text.length);
var lines = notes.readAsLinesSync();
for (var i = 0; i < lines.length; i++) {
print('${i + 1}. ${lines[i]}');
}
}
Reading a CSV File
CSV = "comma-separated values": a table written as text, one row per line.
Average marks from a CSV file
import 'dart:io';
void main() {
var rows = File('marks.csv').readAsLinesSync();
print('Header: ${rows.first}');
for (var row in rows.skip(1)) {
var parts = row.split(',');
var maths = int.parse(parts[1]);
var science = int.parse(parts[2]);
print('${parts[0]}: ${((maths + science) / 2).toStringAsFixed(1)}');
}
}
Simple lesson: read the whole text, the lines, or the bytes, whichever suits your file.
Sync and Async
Every file method has two versions:
| Sync | Async | Difference |
|---|---|---|
readAsStringSync() | readAsString() | async returns a Future<String> |
writeAsStringSync(t) | writeAsString(t) | async returns a Future<File> |
existsSync() | exists() | async returns a Future<bool> |
- Sync waits right there. Simple, fine for small scripts.
- Async lets the program do other work while the disk is busy. Use it in apps and servers. (See Async Basics and Futures.)
The async way with await
import 'dart:io';
Future<void> main() async {
var file = File('notes.txt');
if (await file.exists()) {
var lines = await file.readAsLines();
print('${lines.length} notes found');
print('First: ${lines.first}');
}
}
Simple lesson: ...Sync() waits for the disk, and the version without Sync returns a Future.
Writing Files
Write and Replace
writeAsStringSync(text) creates the file if it is missing, and replaces everything if it exists.
Append
Append = add to the end of a file, keeping what was there. Pass mode: FileMode.append.
Write, then append
Saving a small report
import 'dart:io';
void main() {
var report = File('report.txt');
report.writeAsStringSync('Class 7A report\n');
var marks = {'Asha': 92, 'Ravi': 75, 'Priya': 85};
for (var e in marks.entries) {
report.writeAsStringSync('${e.key}: ${e.value}\n', mode: FileMode.append);
}
print(report.readAsStringSync());
}
Without mode: FileMode.append, every write wipes the file first.
Many beginners lose their saved data this way. Use append when you are adding records.
Simple lesson: a normal write replaces the file, and FileMode.append adds to its end.
File Details
| Method | Gives |
|---|---|
existsSync() | true if the file is there |
lengthSync() | size in bytes |
lastModifiedSync() | a DateTime of the last change |
deleteSync() | removes the file |
renameSync('new.txt') | moves or renames it |
Checking, measuring and deleting
import 'dart:io';
void main() {
var f = File('old_notice.txt');
f.writeAsStringSync('Holiday on Friday');
print(f.existsSync());
print(f.lengthSync());
f.deleteSync();
print(f.existsSync());
}
Simple lesson: check exists before you read, and use length and lastModified to learn about a file.
Directories
Directory = a folder. Use the Directory class.
Making a folder and listing it
import 'dart:io';
void main() {
var reports = Directory('reports/2026');
reports.createSync(recursive: true);
File('reports/2026/7A.txt').writeAsStringSync('Class 7A');
File('reports/2026/7B.txt').writeAsStringSync('Class 7B');
for (var item in reports.listSync()) {
print(item.path);
}
print(Directory('data').listSync().length);
}
recursive: truealso makes the missing parent folders (reports).listSync()gives the files and folders inside.list()is the async version, a Stream.deleteSync(recursive: true)removes a folder with everything in it. Be careful.
Simple lesson: Directory makes, lists and deletes folders, just like File does for files.
When Things Go Wrong
File work fails more than most code: the file is missing, or you are not allowed to touch it.
FileSystemException= the parent type for file problems.PathNotFoundException= a kind ofFileSystemException: the path does not exist.
Handling a missing file and a locked file
import 'dart:io';
void main() {
try {
File('homework.txt').readAsStringSync();
} on PathNotFoundException {
print('homework.txt is missing. Starting a new one.');
}
try {
File('locked.txt').writeAsStringSync('Changed!');
} on FileSystemException catch (e) {
print('Could not write: ${e.message}');
}
}
Easy way to remember
import 'dart:io';first.- read:
readAsString,readAsLines,readAsBytes. - write:
writeAsStringreplaces,mode: FileMode.appendadds. - check:
exists,length,lastModified,delete. - Wrap file work in
trywithon FileSystemException.
Simple lesson: always expect file errors, and catch FileSystemException so the program can recover.
What Is JSON?
JSON (JavaScript Object Notation) = a simple text format for data. Almost every app and server uses it to send data.
- Object =
{ "key": value, ... }. In Dart it becomes aMap<String, dynamic>. - Array =
[value, value, ...]. In Dart it becomes aList<dynamic>. - Values can be strings (in double quotes), numbers,
true,false,null, objects and arrays.
Simple lesson: JSON is data written as text, so any program can read it.
A Real-World Example
The school app shows your marks.
- The marks live on the school's server.
- The server sends them to your phone as JSON text:
{"name": "Asha", "marks": [92, 88]}. - The app decodes the text into Dart values, and shows them on screen.
- When you update your profile, the app encodes your data as JSON and sends it back.
Edit the JSON text and see what Dart gets:
jsonEncode and jsonDecode come from dart:convert. Remember the import.
JSON Structure
| JSON | Dart | Example |
|---|---|---|
object { } | Map<String, dynamic> | {"name": "Asha"} |
array [ ] | List<dynamic> | [92, 88] |
| string | String | "Blue" |
| number | int or double | 13, 1.45 |
true / false | bool | true |
null | null | null |
JSON is strict:
- Keys must be in double quotes:
"name", notnameor'name'. - No comma after the last item.
- No comments.
Simple lesson: JSON has objects, arrays and simple values, and Dart turns them into maps, lists and values.
Encoding and Decoding
jsonEncode
Encode = Dart value → JSON text.
A map becomes JSON text
import 'dart:convert';
void main() {
var student = {'name': 'Asha', 'age': 13, 'monitor': true, 'marks': [92, 88]};
var text = jsonEncode(student);
print(text);
print(text.length);
}
For text that people read, use JsonEncoder.withIndent:
Pretty JSON
import 'dart:convert';
void main() {
var config = {'school': 'Green Valley', 'year': 2026, 'houses': ['Red', 'Blue']};
print(JsonEncoder.withIndent(' ').convert(config));
}
jsonDecode
Decode = JSON text → Dart values.
From text to a map
Simple lesson: jsonEncode writes Dart data as text, and jsonDecode reads text back into Dart data.
Maps, Lists and Nested JSON
jsonDecode returns dynamic. Tell Dart what you expect, so the rest of the code is typed:
Nested JSON from a server
import 'dart:convert';
void main() {
var text = '''
{
"class": "7A",
"teacher": {"name": "Mrs. Iyer", "subject": "Science"},
"students": [
{"name": "Asha", "marks": 92},
{"name": "Ravi", "marks": 75}
]
}
''';
var data = jsonDecode(text) as Map<String, dynamic>;
var teacher = data['teacher'] as Map<String, dynamic>;
var students = data['students'] as List<dynamic>;
print('${data['class']} is taught by ${teacher['name']}');
for (var s in students) {
print('${s['name']}: ${s['marks']}');
}
}
- An object inside an object: read it as another map.
- An array of objects: a list whose items are maps.
Simple lesson: follow the shape of the JSON: maps inside maps, and lists of maps.
Model Classes
Reading s['name'] everywhere is easy to get wrong. A model class gives the data real fields.
fromJson= a factory constructor that builds an object from a map.toJson= a method that turns the object into a map.jsonEncodeautomatically callstoJson()on objects it does not know.
Student.fromJson and toJson
import 'dart:convert';
class Student {
final String name;
final int age;
final List<int> marks;
Student(this.name, this.age, this.marks);
factory Student.fromJson(Map<String, dynamic> json) {
return Student(
json['name'] as String,
json['age'] as int,
(json['marks'] as List).cast<int>(),
);
}
Map<String, dynamic> toJson() => {'name': name, 'age': age, 'marks': marks};
int get total => marks.fold(0, (a, b) => a + b);
}
void main() {
var s = Student.fromJson(jsonDecode('{"name": "Asha", "age": 13, "marks": [92, 88]}'));
print('${s.name} scored ${s.total}');
print(jsonEncode(s));
}
Lists of Objects and Nested Objects
A class with its students
import 'dart:convert';
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
factory Student.fromJson(Map<String, dynamic> j) => Student(j['name'], j['marks']);
Map<String, dynamic> toJson() => {'name': name, 'marks': marks};
}
class Section {
final String code;
final List<Student> students;
Section(this.code, this.students);
factory Section.fromJson(Map<String, dynamic> j) => Section(
j['code'],
(j['students'] as List).map((s) => Student.fromJson(s)).toList(),
);
Map<String, dynamic> toJson() => {'code': code, 'students': students};
}
void main() {
var text = '{"code": "7B", "students": [{"name": "Priya", "marks": 85}, {"name": "Imran", "marks": 79}]}';
var section = Section.fromJson(jsonDecode(text));
print('${section.code} has ${section.students.length} students');
print(section.students.last.name);
print(jsonEncode(section));
}
jsonEncode(section) calls section.toJson(), which holds Student objects, so it calls their toJson() too.
jsonEncode fails on objects with no toJson(), and on things like DateTime and Set.
Convert them first: a date to date.toIso8601String(), and a set to set.toList().
Simple lesson: fromJson turns a map into an object, and toJson turns it back, so the rest of your code uses real fields.
Parsing Safely
Data from outside can be wrong: a key may be missing, a value may be null, or a number may be text.
Bad JSON Text
Catching broken JSON
import 'dart:convert';
void main() {
for (var text in ['{"name": "Asha"}', "{name: 'Asha'}"]) {
try {
var data = jsonDecode(text);
print('OK: ${data['name']}');
} on FormatException {
print('Not valid JSON: $text');
}
}
}
Missing Keys and null
Use the tools from Null Safety: ?, ?? and type checks:
Defaults for missing data
import 'dart:convert';
void main() {
var data = jsonDecode('{"name": "Ravi", "house": null}') as Map<String, dynamic>;
var name = data['name'] as String? ?? 'Unknown';
var house = data['house'] as String? ?? 'Not chosen yet';
var age = data['age'] as int? ?? 0;
print('$name, $house, age $age');
}
Validating with Patterns
A pattern (from Pattern Matching) checks keys and types in one step:
Accepting only well-formed students
import 'dart:convert';
void main() {
var replies = [
'{"name": "Asha", "marks": 92}',
'{"name": "Ravi", "marks": "ninety"}',
'{"marks": 70}',
];
for (var r in replies) {
var data = jsonDecode(r);
if (data case {'name': String name, 'marks': int marks}) {
print('Saved $name with $marks');
} else {
print('Rejected: $r');
}
}
}
Where JSON Is Used
| Use | Example |
|---|---|
| API responses | the server sends a student list |
| Config files | config.json with the school name and settings |
| Local storage | saving a user's choices to a file |
Easy way to remember
import 'dart:convert';- encode = Dart → text. decode = text → Dart.
- object →
Map<String, dynamic>, array →List<dynamic>. - fromJson in, toJson out.
- Never trust outside data: catch
FormatException, use??, and check with patterns.
Simple lesson: treat outside JSON as untrusted, and check it before you use it.
TL;DR
dart:iogivesFileandDirectory. Not available on the web.- Read:
readAsString,readAsLines. Write:writeAsString,FileMode.append. Each has a Sync version. - Handle
FileSystemExceptionfor missing or locked files. - JSON (
dart:convert):jsonEncode→ text,jsonDecode→ maps and lists. - Model classes:
fromJsonandtoJson. Parse safely with??, type checks and patterns.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Forgetting import 'dart:io'; | File is built in | File comes from dart:io | Undefined name 'File'. |
| Writing without append | Writes add to the file | A normal write replaces it | Old data is lost. |
| Reading without checking | The file is always there | Check exists or catch the error | PathNotFoundException. |
Mixing readAsString() with no await | It gives the text | It gives a Future<String> | You print Instance of 'Future<String>'. |
createSync() on a/b/c | Parent folders appear | Pass recursive: true | A PathNotFoundException. |
Using dart:io in a web app | It works everywhere | Browsers have no file system access like this | The import fails. |
Forgetting import 'dart:convert'; | JSON tools are built in | Import dart:convert | Method not found: 'jsonEncode'. |
| Single quotes in JSON text | JSON is like Dart | JSON needs double quotes | FormatException. |
Reading data.name | Decoded JSON has fields | It is a map: data['name'] | A compile error, or NoSuchMethodError. |
Encoding an object with no toJson() | Dart knows how | Write toJson() | Converting object to an encodable object failed. |
| Trusting every key | The server is always right | Use ?? and type checks | A crash on missing data. |
json['marks'] as List<int> | The cast converts | Use (json['marks'] as List).cast<int>() | A type error. |
Check Yourself
Exercises
What to Remember
- Files keep data after the program ends.
- A normal write replaces a file, and
FileMode.appendadds to it. - Expect file errors, and catch
FileSystemException. - JSON is text data, and
jsonEncodeandjsonDecodeconvert it. - Model classes with
fromJsonandtoJsonkeep JSON code clean.
Next: Dart Cheat Sheet. All the syntax from this course on one page.