On this page25 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. The File Class
- 2. Writing Text with writeAsString
- 3. Reading Text with readAsString
- 4. Writing Replaces the Old Content
- 5. Adding to a File with FileMode.append
- 6. The Synchronous Versions
- 7. Lists of Lines: join and readAsLines
- 8. exists and delete
- 9. A Missing File Throws
- 10. Catching FileSystemException
- 11. Writing Piece by Piece with openWrite
- 12. Reading a Large File Line by Line
- 13. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 106 — File Handling
| Category | 18 — Files, JSON & Core Utilities |
| Lecture | 106 of 140 |
| Estimated video length | 12 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
Every value in our programs disappears the moment
mainends — Ali's marks, Sara's balance, all gone. A file keeps data on the disk, so the next run can read it back.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every output and error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2. Long stack traces are shortened to their first lines.
Prerequisites
Students should already understand, from Lectures 043–101:
join,split,mapandreduceon lists, and mapentries(Lectures 043–050)try/catchandon SomeException catch (e)(Lecture 084)Future,async/await, andtry/catcharoundawait(Lectures 088–090)- streams,
await forandtransform(Lectures 092 and 095) - importing core libraries such as
dart:io(Lecture 101)
Connection With Previous Lectures
Here is Ali's mark list from many earlier lectures:
void main() {
var marks = [80, 75, 85];
print(marks);
}[80, 75, 85]Run it again tomorrow and the list is rebuilt from the code — any mark added while the
program ran is lost. Variables live in memory, and memory is cleared when the program stops.
In Lecture 101 we saw that dart:io holds tools for files. Now we use them to make data
outlive the program.
Learning Objectives
By the end of this lecture you will be able to:
- Create a
Fileobject from a file name - Write and read text with
writeAsStringandreadAsString - Add to a file with
FileMode.appendinstead of replacing it - Use the synchronous versions
writeAsStringSyncandreadAsStringSync - Save a list as lines and read it back with
readAsLines - Check for a file with
existsand remove it withdelete - Catch a
FileSystemExceptionwhen a file is missing - Write piece by piece with
openWrite, and read a large file line by line withopenRead
1. The File Class
File comes from dart:io. File('notes.txt') creates an object that points to a file
name. It does not touch the disk yet.
Analogy — a
Fileobject is like a label for a drawer in a cabinet. Writing the label does not put anything in the drawer; you still have to open it and put papers in or take them out.
import 'dart:io';
void main() {
var file = File('notes.txt');
print(file.path);
print(file);
}notes.txt
File: 'notes.txt'A plain name like 'notes.txt' is a relative path: the file lives in the folder you ran
dart run from. We will study folders and full paths in Lecture 107.
2. Writing Text with writeAsString
Disk work is slow compared with memory, so file methods return a Future (Lecture 088).
We await them:
import 'dart:io';
Future<void> main() async {
var file = File('notes.txt');
await file.writeAsString('Hello, Ali!');
print('Saved to ${file.path}');
}Saved to notes.txtIf notes.txt does not exist, writeAsString creates it. Open the folder and you will find
the file with the text inside.
3. Reading Text with readAsString
readAsString returns a Future<String> — the whole file as one string:
import 'dart:io';
Future<void> main() async {
var file = File('notes.txt');
await file.writeAsString('Hello, Ali!');
var text = await file.readAsString();
print(text);
print(text.length);
}Hello, Ali!
11Once read, text is an ordinary String, with length, split and everything else.
4. Writing Replaces the Old Content
By default, writeAsString overwrites the file:
import 'dart:io';
Future<void> main() async {
var file = File('notes.txt');
await file.writeAsString('First note');
await file.writeAsString('Second note');
print(await file.readAsString());
}Second noteFirst note is gone. That is right for a settings file, but wrong for a log.
5. Adding to a File with FileMode.append
Pass mode: FileMode.append to add to the end instead:
import 'dart:io';
Future<void> main() async {
var file = File('marks.txt');
await file.writeAsString('Mon: 80\n');
await file.writeAsString('Tue: 75\n', mode: FileMode.append);
await file.writeAsString('Wed: 90\n', mode: FileMode.append);
stdout.write(await file.readAsString());
}Mon: 80
Tue: 75
Wed: 90The first write has no mode, so it starts the file fresh. Append does not add a line break
for you — the \n at the end of each string does. We use stdout.write (Lecture 101) because
the text already ends with \n, and print would add an extra blank line.
6. The Synchronous Versions
Each method has a Sync twin that returns the value directly and needs no await:
import 'dart:io';
void main() {
var file = File('settings.txt');
file.writeAsStringSync('theme=dark');
var text = file.readAsStringSync();
print(text);
}theme=darkA Sync call blocks: nothing else runs until the disk finishes (Lecture 087). That is
fine for a small script; in an app that must stay responsive, prefer the async versions.
7. Lists of Lines: join and readAsLines
To save a list, join it with \n. To get it back, readAsLines splits the file into a
List<String>:
import 'dart:io';
Future<void> main() async {
var file = File('students.txt');
var names = ['Ali', 'Sara', 'Zayan'];
await file.writeAsString(names.join('\n'));
var lines = await file.readAsLines();
print(lines);
print(lines.length);
print(lines[1]);
}[Ali, Sara, Zayan]
3
SaraA file that ends with \n does not produce an empty last line, so readAsLines works with
both styles.
8. exists and delete
exists() answers whether the file is there; delete() removes it. Both return futures:
import 'dart:io';
Future<void> main() async {
var file = File('temp.txt');
print(await file.exists());
await file.writeAsString('temporary');
print(await file.exists());
await file.delete();
print(await file.exists());
}false
true
false9. A Missing File Throws
Reading a file that does not exist is an exception, not a bug in your code — the user may have deleted it:
import 'dart:io';
Future<void> main() async {
var text = await File('missing.txt').readAsString();
print(text);
}Unhandled exception:
PathNotFoundException: Cannot open file, path = 'missing.txt' (OS Error: No such file or directory, errno = 2)
#0 _checkForErrorResponse (dart:io/common.dart:58:9)The program crashes. PathNotFoundException is one kind of FileSystemException, the
type for every file problem: missing file, no permission, disk full.
10. Catching FileSystemException
Exactly as in Lecture 084, wrap the await in try and catch the type with on:
import 'dart:io';
Future<void> main() async {
try {
var text = await File('missing.txt').readAsString();
print(text);
} on FileSystemException catch (e) {
print('Could not read the file.');
print(e.message);
print(e.path);
print(e.osError?.message);
}
print('Program continues');
}Could not read the file.
Cannot open file
missing.txt
No such file or directory
Program continuesmessage says what Dart tried, path names the file, and osError holds the operating
system's reason. osError may be null, hence ?. (Lecture 014). Catching
FileSystemException also catches PathNotFoundException, because it is a kind of
FileSystemException.
11. Writing Piece by Piece with openWrite
For many small writes, openWrite() gives an IOSink — an open file you keep writing to.
It has the same write and writeln as stdout:
import 'dart:io';
Future<void> main() async {
var file = File('report.txt');
var sink = file.openWrite();
sink.writeln('Report for Sara');
for (var test = 1; test <= 3; test++) {
sink.writeln('Test $test: ${70 + test * 5}');
}
await sink.close();
stdout.write(await file.readAsString());
}Report for Sara
Test 1: 75
Test 2: 80
Test 3: 85writeln does not wait; the sink collects the text. await sink.close() writes everything
out and closes the file — always close a sink. openWrite(mode: FileMode.append) adds to
an existing file.
12. Reading a Large File Line by Line
readAsString loads the whole file into memory. For a huge log, read it as a stream
(Lecture 092) instead. openRead() delivers raw bytes; utf8.decoder (from dart:convert)
turns bytes into text, and LineSplitter cuts the text into lines:
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
var file = File('log.txt');
var sink = file.openWrite();
for (var i = 1; i <= 100000; i++) {
sink.writeln('Entry $i');
}
await sink.close();
print('Size: ${await file.length()} bytes');
var lines = file
.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter());
var count = 0;
await for (var line in lines) {
count++;
if (count <= 2) print(line);
}
print('Lines: $count');
}Size: 1188895 bytes
Entry 1
Entry 2
Lines: 100000Only a small piece of the file is in memory at any moment. transform is the same method
from Lecture 095. We will study dart:convert and text encodings with JSON in Lecture 108.
13. A Real-World Example
Ali keeps his marks in a file, one subject,mark per line. Sara has no file yet:
import 'dart:io';
Future<void> addMark(File file, String subject, int mark) async {
await file.writeAsString('$subject,$mark\n', mode: FileMode.append);
}
Future<void> main() async {
var file = File('ali_marks.txt');
if (await file.exists()) {
await file.delete();
}
await addMark(file, 'Math', 80);
await addMark(file, 'Science', 75);
await addMark(file, 'English', 85);
var total = 0;
for (var line in await file.readAsLines()) {
var parts = line.split(',');
var mark = int.parse(parts[1]);
print('${parts[0]}: $mark');
total += mark;
}
print('Total: $total');
print('Average: ${total / 3}');
try {
await File('sara_marks.txt').readAsString();
} on FileSystemException catch (e) {
print('Sara: ${e.message} (${e.path})');
}
}Math: 80
Science: 75
English: 85
Total: 240
Average: 80.0
Sara: Cannot open file (sara_marks.txt)exists and delete start a clean file, append adds each mark, readAsLines reads them
back, and on FileSystemException handles the missing file without a crash.
Code Examples
Example 1 — Write and Read a Note
import 'dart:io';
Future<void> main() async {
var file = File('note.txt');
await file.writeAsString('Buy milk');
var note = await file.readAsString();
print('Note: $note');
}Note: Buy milkExample 2 — Overwrite vs Append
import 'dart:io';
Future<void> main() async {
var file = File('visits.txt');
await file.writeAsString('Ali\n');
await file.writeAsString('Sara\n');
print(await file.readAsLines());
await file.writeAsString('Zayan\n', mode: FileMode.append);
print(await file.readAsLines());
}[Sara]
[Sara, Zayan]Example 3 — A Sync Counter
import 'dart:io';
void main() {
var file = File('counter.txt');
file.writeAsStringSync('1');
var count = int.parse(file.readAsStringSync());
file.writeAsStringSync('${count + 1}');
print('Count: ${file.readAsStringSync()}');
}Count: 2Example 4 — Numbered Lines
import 'dart:io';
Future<void> main() async {
var file = File('fruits.txt');
await file.writeAsString(['apple', 'mango', 'banana'].join('\n'));
var fruits = await file.readAsLines();
for (var i = 0; i < fruits.length; i++) {
print('${i + 1}. ${fruits[i]}');
}
}1. apple
2. mango
3. bananaExample 5 — Delete Only If It Exists
import 'dart:io';
Future<void> main() async {
var file = File('draft.txt');
await file.writeAsString('draft');
if (await file.exists()) {
await file.delete();
print('Deleted ${file.path}');
}
print('Still there? ${await file.exists()}');
}Deleted draft.txt
Still there? falseExample 6 — A Fallback for a Missing File
import 'dart:io';
Future<String> loadName() async {
try {
return await File('name.txt').readAsString();
} on FileSystemException {
return 'Guest';
}
}
Future<void> main() async {
print('Welcome, ${await loadName()}');
await File('name.txt').writeAsString('Sara');
print('Welcome, ${await loadName()}');
}Welcome, Guest
Welcome, Saraon FileSystemException without catch (e) handles the error when we don't need its details.
Example 7 — Student Marks
import 'dart:io';
Future<void> main() async {
var file = File('sara_marks.txt');
await file.writeAsString('88\n92\n79\n');
var marks = (await file.readAsLines()).map(int.parse).toList();
var total = marks.reduce((a, b) => a + b);
print('Marks: $marks');
print('Total: $total');
print('Average: ${(total / marks.length).toStringAsFixed(1)}');
}Marks: [88, 92, 79]
Total: 259
Average: 86.3How it works
readAsLinesgives['88', '92', '79']— strings, not numbers.map(int.parse)converts each line, andtoList()makes aList<int>.
Example 8 — Shopping Receipt with openWrite
import 'dart:io';
Future<void> main() async {
var cart = {'Notebook': 500, 'Pen': 50, 'Bag': 1500};
var file = File('receipt.txt');
var sink = file.openWrite();
var total = 0;
for (var item in cart.entries) {
sink.writeln('${item.key}: ${item.value}');
total += item.value;
}
sink.writeln('Total: $total');
await sink.close();
stdout.write(await file.readAsString());
}Notebook: 500
Pen: 50
Bag: 1500
Total: 2050Example 9 — Bank Transaction Log
import 'dart:io';
Future<void> main() async {
var log = File('bank_log.txt');
await log.writeAsString('');
Future<void> record(int amount) =>
log.writeAsString('$amount\n', mode: FileMode.append);
await record(1000);
await record(-250);
await record(400);
var entries = await log.readAsLines();
var balance = 0;
for (var line in entries) {
balance += int.parse(line);
}
print('Entries: ${entries.length}');
print('Balance: $balance');
}Entries: 3
Balance: 1150How it works
- Writing
''empties the log, so every run starts the same. - Each
recordappends one amount; withdrawals are negative. - The balance is rebuilt from the log — the file is the record of truth.
Example 10 — Hottest Temperature, Streamed
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
var file = File('temps.txt');
await file.writeAsString('31.5\n29.0\n35.2\n33.8\n');
var hottest = -100.0;
var lines = file
.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter());
await for (var line in lines) {
var t = double.parse(line);
if (t > hottest) hottest = t;
}
print('Hottest: $hottest');
}Hottest: 35.2Common Beginner Mistakes
1. Forgetting await
import 'dart:io';
Future<void> main() async {
var file = File('notes.txt');
await file.writeAsString('Hello');
print(file.readAsString()); // ✗ no await
}Instance of 'Future<String>'Write print(await file.readAsString());.
2. Using await in a plain main
import 'dart:io';
void main() {
var file = File('notes.txt');
file.writeAsStringSync('Hi');
// var text = await file.readAsString(); // ✗
}Error: 'await' can only be used in 'async' or 'async*' methods.Either make main Future<void> main() async, or use readAsStringSync().
3. Writing a number instead of a string
import 'dart:io';
Future<void> main() async {
var file = File('score.txt');
// await file.writeAsString(80); // ✗
}Error: The argument type 'int' can't be assigned to the parameter type 'String'.Files hold text here: write '80' or '$score', and int.parse it when reading.
4. Overwriting a log by accident
Calling writeAsString without mode: FileMode.append in a loop leaves only the last line.
There is no error — the data is just gone (Step 4).
5. Writing after close()
import 'dart:io';
Future<void> main() async {
var sink = File('report.txt').openWrite();
sink.writeln('Report for Sara');
await sink.close();
// sink.writeln('Test 1: 75'); // ✗
}Unhandled exception:
Bad state: StreamSink is closedDo all the writing first, then close() once at the end.
6. Reading a file without handling a missing one
A file name in your code is no promise the file is there. Without try/catch, a missing
file crashes the program with PathNotFoundException (Step 9).
Best Practices
- Prefer the async methods with
await; keepSyncversions for small scripts. - Use
FileMode.appendfor logs and history; plain writes for settings and snapshots. - End each appended line with
\n, soreadAsLinessees separate lines. - Wrap reads in
trywithon FileSystemException, and give the user a fallback. - Always
await sink.close()afteropenWrite. - Read large files with
openReadandLineSplitter, notreadAsString.
Mini Challenge
Zayan keeps a diary. Start diary.txt empty, append the entries Played cricket,
Read a book and Helped Mum in a loop, then print how many entries there are and the last
one.
Your program should print exactly:
Entries: 3
Last: Helped MumTry it yourself before opening the solution.
Show solution
import 'dart:io';
Future<void> main() async {
var diary = File('diary.txt');
await diary.writeAsString('');
for (var entry in ['Played cricket', 'Read a book', 'Helped Mum']) {
await diary.writeAsString('$entry\n', mode: FileMode.append);
}
var lines = await diary.readAsLines();
print('Entries: ${lines.length}');
print('Last: ${lines.last}');
}Entries: 3
Last: Helped MumHow it works
- Writing
''empties the diary, so a second run doesn't double the entries. - Each entry is appended with its own
\n. readAsLinesreturns three lines;lastis the newest entry.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Write
Hello, Sara!togreeting.txt, read it back withreadAsString, and print it. - Using only the
Syncmethods, writeZayantoname.txtand print the file's content. - Write
Alitofull_name.txt, then appendKhan(no line break) and printAli Khan.
Application
- Save the marks
[80, 75, 90]tomarks.txt, one per line. Read them withreadAsLinesand printTotal: 245. - Write
Opening: 1000tobank.txt, then appendDeposit: 500andWithdraw: 200, each on its own line. Print the whole file. - For
old.txt, print whether it exists, create it, print again, delete it, and print again. Expected:false,true,false. - Read
config.txt, which does not exist. Catch the error and printNo config, using defaults.
Challenge
- With
openWrite, write one line per Celsius value in[0, 10, 20, 30], like0 C = 32.0 F. Close the sink, then read the lines back and print them. - Write
Notebook,500,Pen,50andBag,1500tocart.txt. Read it withopenRead,utf8.decoderandLineSplitter, and printTotal: 2050. - Write
Future<String> readOrDefault(String path, String fallback)that returns the file's text, orfallbackwhen the file cannot be read. Test it on an existingmotto.txt(Keep learning) and a missingnothing.txt.
Practice Answers
Question 1 — Solution
import 'dart:io';
Future<void> main() async {
var file = File('greeting.txt');
await file.writeAsString('Hello, Sara!');
print(await file.readAsString());
}Hello, Sara!Question 2 — Solution
import 'dart:io';
void main() {
var file = File('name.txt');
file.writeAsStringSync('Zayan');
print(file.readAsStringSync());
}ZayanQuestion 3 — Solution
import 'dart:io';
Future<void> main() async {
var file = File('full_name.txt');
await file.writeAsString('Ali');
await file.writeAsString(' Khan', mode: FileMode.append);
print(await file.readAsString());
}Ali KhanQuestion 4 — Solution
import 'dart:io';
Future<void> main() async {
var file = File('marks.txt');
var marks = [80, 75, 90];
await file.writeAsString(marks.join('\n'));
var total = 0;
for (var line in await file.readAsLines()) {
total += int.parse(line);
}
print('Total: $total');
}Total: 245Question 5 — Solution
import 'dart:io';
Future<void> main() async {
var file = File('bank.txt');
await file.writeAsString('Opening: 1000\n');
await file.writeAsString('Deposit: 500\n', mode: FileMode.append);
await file.writeAsString('Withdraw: 200\n', mode: FileMode.append);
stdout.write(await file.readAsString());
}Opening: 1000
Deposit: 500
Withdraw: 200Question 6 — Solution
import 'dart:io';
Future<void> main() async {
var file = File('old.txt');
print(await file.exists());
await file.writeAsString('old data');
print(await file.exists());
await file.delete();
print(await file.exists());
}false
true
falseQuestion 7 — Solution
import 'dart:io';
Future<void> main() async {
try {
print(await File('config.txt').readAsString());
} on FileSystemException {
print('No config, using defaults');
}
}No config, using defaultsQuestion 8 — Solution
import 'dart:io';
Future<void> main() async {
var file = File('temperatures.txt');
var sink = file.openWrite();
for (var c in [0, 10, 20, 30]) {
sink.writeln('$c C = ${c * 9 / 5 + 32} F');
}
await sink.close();
for (var line in await file.readAsLines()) {
print(line);
}
}0 C = 32.0 F
10 C = 50.0 F
20 C = 68.0 F
30 C = 86.0 FQuestion 9 — Solution
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
var file = File('cart.txt');
await file.writeAsString('Notebook,500\nPen,50\nBag,1500\n');
var total = 0;
var lines = file
.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter());
await for (var line in lines) {
total += int.parse(line.split(',')[1]);
}
print('Total: $total');
}Total: 2050Question 10 — Solution
import 'dart:io';
Future<String> readOrDefault(String path, String fallback) async {
try {
return await File(path).readAsString();
} on FileSystemException {
return fallback;
}
}
Future<void> main() async {
await File('motto.txt').writeAsString('Keep learning');
print(await readOrDefault('motto.txt', 'No motto'));
print(await readOrDefault('nothing.txt', 'No motto'));
}Keep learning
No mottoThe return await matters: without await, the failing future would leave the try before
its error arrives, and the catch would never see it.
Quick Revision
- Does
File('notes.txt')create a file on the disk? - What does
writeAsStringdo to text already in the file? - How do you add to the end of a file instead?
- What is the difference between
readAsStringandreadAsStringSync? - What does
readAsLinesreturn? - Which exception type covers a missing file?
- What must you always do after writing with
openWrite? - Why read a huge file with
openReadinstead ofreadAsString?
Key Takeaways
File('name.txt')fromdart:iopoints to a file; a relative name lives in the folder you run from.writeAsStringcreates or replaces a file;readAsStringreturns its text — both are futures, soawaitthem.mode: FileMode.appendadds to the end; add\nyourself.Syncversions return values directly but block the program.join('\n')saves a list;readAsLinesreads it back asList<String>.exists()anddelete()check for and remove a file.- A missing file throws
PathNotFoundException, aFileSystemException— catch it withon. openWrite()gives anIOSinkfor many writes — alwaysawait sink.close().openRead()+utf8.decoder+LineSplitterstreams a large file line by line.
What's Next?
Lecture 107 — Directories & Paths. Every file in this lecture landed in the folder we ran from. Real programs keep files in folders of their own. Next we will create and list directories, and build file paths that work on every operating system.