On this page25 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Are Directories and Paths?
- 2. Where Am I? — Directory.current
- 3. Relative vs Absolute Paths
- 4. The Path Separator
- 5. A Safe Playground — Temporary Directories
- 6. Creating a Directory
- 7. Nested Folders — recursive: true
- 8. Listing a Directory — list()
- 9. listSync() and recursive: true
- 10. File or Folder? — FileSystemEntity
- 11. Building Paths Safely — package:path
- 12. Deleting a Directory
- 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 107 — Directories & Paths
| Category | 18 — Files, JSON & Core Utilities |
| Lecture | 107 of 140 |
| Estimated video length | 9 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
In Lecture 106 every file landed in one place, and
'notes.txt'was somewhere we never checked. Directories let a program build folders, look inside them and clean them up — and paths tell it exactly where everything lives.
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, produced on Dart 3.12.2 on macOS. Full paths differ on every computer, so they are shown as placeholders:
<project>is the folder you run the program from, and<temp>is a temporary folder Dart creates (its name ends in random letters). On Windows you will see\instead of/.
Prerequisites
Students should already understand, from Lectures 089–106:
async/awaitandFuture<void> main() async(Lecture 089)try/catcharound anawait(Lecture 090)await forover a stream (Lecture 092)import ... asprefixes (Lecture 101)- adding a package to
pubspec.yaml(Lectures 104–105) dart:io,File,writeAsString,readAsString,existsanddelete(Lecture 106)
Connection With Previous Lectures
In Lecture 106, Ali saved marks like this:
import 'dart:io';
Future<void> main() async {
await File('ali.txt').writeAsString('Ali: 80');
}It works, but two questions remain. Where is ali.txt on the disk? And with 300 students,
how do we put the files into a reports folder, list them, and delete them later? A File
cannot answer those. A Directory and a correct path can.
Learning Objectives
By the end of this lecture you will be able to:
- Understand what a directory and a path are
- Find the working directory with
Directory.current - Tell relative paths from absolute paths
- Use
Platform.pathSeparatorandpackage:pathto build paths safely - Create a safe playground with
Directory.systemTemp.createTemp - Create nested folders with
create(recursive: true) - List a folder with
list()andlistSync(), and tell files from folders - Delete a folder, including one that is not empty
1. What Are Directories and Paths?
A directory (folder) holds files and other directories. A path is the text that says where something is.
Analogy — a path is a street address.
reports/2026/ali.txtmeans: go to thereportsbuilding, then floor2026, then the door labelledali.txt.
In Dart, folders come from dart:io as Directory objects, just as files are File
objects.
2. Where Am I? — Directory.current
Every running program has a working directory: the folder it was started from.
import 'dart:io';
void main() {
print(Directory.current.path);
}<project>When you run dart run inside your project folder, this prints that folder's full path.
3. Relative vs Absolute Paths
An absolute path starts at the top of the disk (/ on macOS and Linux, a drive like
C:\ on Windows). A relative path is measured from the working directory.
import 'dart:io';
void main() {
File relative = File('notes.txt');
print(relative.path);
print(relative.isAbsolute);
print(relative.absolute.path);
print(relative.absolute.isAbsolute);
}notes.txt
false
<project>/notes.txt
trueThat answers the Connection question: Lecture 106's ali.txt was written into the working
directory. Run the program from another folder and the file goes there instead.
4. The Path Separator
macOS and Linux separate folder names with /; Windows uses \. Platform.pathSeparator
gives the right one for the computer the program is running on.
import 'dart:io';
void main() {
print(Platform.pathSeparator);
print(Platform.operatingSystem);
}/
macosOn Windows the same program prints \ and windows.
5. A Safe Playground — Temporary Directories
Practising delete on real folders is risky. Directory.systemTemp is the operating
system's temporary folder, and createTemp makes a brand-new, empty folder inside it with a
unique name.
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
print(temp.path);
print(await temp.exists());
await temp.delete();
print(await temp.exists());
}<temp>/lesson_rXVLa9
true
falseThe letters after lesson_ are random, so yours will differ. Every demo below works inside
such a folder and deletes it at the end.
6. Creating a Directory
create() makes the folder. exists() checks for it, just like on a File.
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
String sep = Platform.pathSeparator;
Directory reports = Directory('${temp.path}${sep}reports');
await reports.create();
print(await reports.exists());
await temp.delete(recursive: true);
}trueEach async method also has a Sync twin — createSync(), existsSync(), deleteSync() —
as you saw for files in Lecture 106.
7. Nested Folders — recursive: true
Plain create() makes only the last folder; every parent must already exist.
recursive: true creates all missing parents, and does nothing if the folder is already
there.
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
String sep = Platform.pathSeparator;
Directory deep = Directory('${temp.path}${sep}2026${sep}march${sep}week1');
await deep.create(recursive: true);
print(await deep.exists());
await deep.create(recursive: true); // already there — no error
print('Still fine');
await temp.delete(recursive: true);
}true
Still fineWithout recursive: true, the same create() fails — see Common Mistakes.
8. Listing a Directory — list()
list() returns a Stream<FileSystemEntity>: one item per file or folder inside. Read it
with await for.
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
String sep = Platform.pathSeparator;
await File('${temp.path}${sep}ali.txt').writeAsString('Ali: 80');
await File('${temp.path}${sep}sara.txt').writeAsString('Sara: 92');
await Directory('${temp.path}${sep}archive').create();
await for (FileSystemEntity entity in temp.list()) {
print(entity.path.split(sep).last);
}
await temp.delete(recursive: true);
}sara.txt
archive
ali.txtThe order is not guaranteed — it depends on the operating system. Sort when order matters.
9. listSync() and recursive: true
listSync() returns a plain List<FileSystemEntity> at once. With recursive: true, both
versions also look inside every sub-folder.
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
String sep = Platform.pathSeparator;
await File('${temp.path}${sep}ali.txt').writeAsString('Ali: 80');
await Directory('${temp.path}${sep}archive').create();
await File('${temp.path}${sep}archive${sep}old.txt').writeAsString('old');
List<FileSystemEntity> top = temp.listSync();
List<FileSystemEntity> all = temp.listSync(recursive: true);
print('Top level: ${top.length}');
print('Everything: ${all.length}');
await temp.delete(recursive: true);
}Top level: 2
Everything: 3The top level holds ali.txt and archive; recursion adds archive/old.txt.
10. File or Folder? — FileSystemEntity
FileSystemEntity is the parent class of File, Directory and Link (a shortcut to
another path). Use is to find out which one you have.
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
String sep = Platform.pathSeparator;
await File('${temp.path}${sep}ali.txt').writeAsString('Ali: 80');
await Directory('${temp.path}${sep}archive').create();
List<FileSystemEntity> entries = temp.listSync();
entries.sort((a, b) => a.path.compareTo(b.path));
for (FileSystemEntity entity in entries) {
String name = entity.path.split(sep).last;
if (entity is File) {
print('File: $name');
} else if (entity is Directory) {
print('Folder: $name');
}
}
await temp.delete(recursive: true);
}File: ali.txt
Folder: archiveFor a bare path string, FileSystemEntity.type answers the same question — including
"nothing there":
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
String sep = Platform.pathSeparator;
await File('${temp.path}${sep}ali.txt').writeAsString('Ali: 80');
print(await FileSystemEntity.type('${temp.path}${sep}ali.txt'));
print(await FileSystemEntity.type(temp.path));
print(await FileSystemEntity.type('${temp.path}${sep}missing.txt'));
await temp.delete(recursive: true);
}file
directory
notFound11. Building Paths Safely — package:path
Gluing strings with Platform.pathSeparator works, but it is noisy and easy to get wrong.
The official path package does it for you. Add it as in Lecture 105
(dart pub add path), then import it with a prefix:
import 'package:path/path.dart' as p;
void main() {
String file = p.join('reports', '2026', 'ali.txt');
print(file);
print(p.basename(file));
print(p.basenameWithoutExtension(file));
print(p.extension(file));
print(p.dirname(file));
print(p.isAbsolute(file));
}reports/2026/ali.txt
ali.txt
ali
.txt
reports/2026
falsep.join uses the correct separator for the platform and never doubles or forgets one. The
as p prefix is the convention, because names like join and extension are too common to
import bare.
12. Deleting a Directory
delete() refuses a folder that still has something inside. delete(recursive: true)
removes the folder and everything in it.
import 'dart:io';
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('lesson_');
String sep = Platform.pathSeparator;
await File('${temp.path}${sep}ali.txt').writeAsString('Ali: 80');
await temp.delete(recursive: true);
print(await temp.exists());
}falseThere is no recycle bin. Only use recursive: true on a path you are certain about.
13. A Real-World Example
Sara's school keeps one folder per class. The program builds the folders, saves one report per student, then lists them in order.
import 'dart:io';
import 'package:path/path.dart' as p;
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('school_');
Map<String, int> classA = {'Ali': 80, 'Sara': 92};
Directory folder = Directory(p.join(temp.path, 'reports', 'class_a'));
await folder.create(recursive: true);
for (MapEntry<String, int> entry in classA.entries) {
File file = File(p.join(folder.path, '${entry.key.toLowerCase()}.txt'));
await file.writeAsString('${entry.key}: ${entry.value}');
}
List<File> files = folder.listSync().whereType<File>().toList();
files.sort((a, b) => a.path.compareTo(b.path));
for (File file in files) {
print('${p.basename(file.path)} -> ${await file.readAsString()}');
}
await temp.delete(recursive: true);
}ali.txt -> Ali: 80
sara.txt -> Sara: 92Create the folders, build each path with p.join, write, list, sort, clean up.
Code Examples
The examples from 6 onwards use package:path, so run them in a project that depends on it.
Example 1 — Working Directory
import 'dart:io';
void main() {
print(Directory.current.isAbsolute);
print(Directory('data').isAbsolute);
}true
falseExample 2 — Making a Path Absolute
import 'dart:io';
void main() {
Directory data = Directory('data');
print(data.path);
print(data.absolute.path);
}data
<project>/dataExample 3 — Synchronous Playground
import 'dart:io';
void main() {
Directory temp = Directory.systemTemp.createTempSync('sync_');
print(temp.existsSync());
temp.deleteSync();
print(temp.existsSync());
}true
falseExample 4 — Nested Folders, Synchronously
import 'dart:io';
void main() {
Directory temp = Directory.systemTemp.createTempSync('nest_');
String sep = Platform.pathSeparator;
Directory deep = Directory('${temp.path}${sep}a${sep}b${sep}c');
deep.createSync(recursive: true);
print(deep.existsSync());
temp.deleteSync(recursive: true);
}trueExample 5 — Counting Only Files
import 'dart:io';
void main() {
Directory temp = Directory.systemTemp.createTempSync('count_');
String sep = Platform.pathSeparator;
File('${temp.path}${sep}one.txt').writeAsStringSync('1');
File('${temp.path}${sep}two.txt').writeAsStringSync('2');
Directory('${temp.path}${sep}sub').createSync();
int files = temp.listSync().whereType<File>().length;
print('Files: $files');
temp.deleteSync(recursive: true);
}Files: 2Example 6 — Taking a Path Apart
import 'package:path/path.dart' as p;
void main() {
print(p.split('reports/2026/ali.txt'));
print(p.join('reports/', 'ali.txt'));
print(p.relative('/school/reports/ali.txt', from: '/school'));
}[reports, 2026, ali.txt]
reports/ali.txt
reports/ali.txtp.join notices the trailing / on 'reports/' and does not add a second one.
Example 7 — Student Report Folders
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
Directory temp = Directory.systemTemp.createTempSync('students_');
for (String name in ['Zayan', 'Ali', 'Sara']) {
Directory(p.join(temp.path, name)).createSync();
}
List<String> names = temp
.listSync()
.map((entity) => p.basename(entity.path))
.toList();
names.sort();
print(names);
temp.deleteSync(recursive: true);
}[Ali, Sara, Zayan]Example 8 — Shopping Receipts Total
import 'dart:io';
import 'package:path/path.dart' as p;
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('receipts_');
Map<String, int> receipts = {'monday': 450, 'tuesday': 1200, 'friday': 300};
for (MapEntry<String, int> r in receipts.entries) {
await File(p.join(temp.path, '${r.key}.txt')).writeAsString('${r.value}');
}
int total = 0;
await for (FileSystemEntity entity in temp.list()) {
if (entity is File) {
total += int.parse(await entity.readAsString());
}
}
print('Total spent: $total');
await temp.delete(recursive: true);
}Total spent: 1950The list() order does not matter here, because addition gives the same total in any order.
Example 9 — Bank Statements by Year
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
Directory temp = Directory.systemTemp.createTempSync('bank_');
for (String year in ['2025', '2026']) {
String folder = p.join(temp.path, 'statements', year);
Directory(folder).createSync(recursive: true);
File(p.join(folder, 'jan.txt')).writeAsStringSync('1000');
}
List<FileSystemEntity> all = temp.listSync(recursive: true);
print('Folders: ${all.whereType<Directory>().length}');
print('Files: ${all.whereType<File>().length}');
temp.deleteSync(recursive: true);
}Folders: 3
Files: 2The three folders are statements, statements/2025 and statements/2026.
Example 10 — Cleaning Old Temperature Logs
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
Directory temp = Directory.systemTemp.createTempSync('logs_');
for (String name in ['monday.log', 'monday.tmp', 'tuesday.tmp']) {
File(p.join(temp.path, name)).writeAsStringSync('25.0');
}
for (File file in temp.listSync().whereType<File>()) {
if (p.extension(file.path) == '.tmp') {
file.deleteSync();
}
}
List<String> left = temp
.listSync()
.map((entity) => p.basename(entity.path))
.toList();
print(left);
temp.deleteSync(recursive: true);
}[monday.log]Common Beginner Mistakes
1. Creating a nested folder without recursive: true
// Directory('${temp.path}${sep}2026${sep}march${sep}week1').create(); // ✗PathNotFoundException: Creation failed, path = '<temp>/lesson_Mw7tAF/2026/march/week1' (OS Error: No such file or directory, errno = 2)2026 and march did not exist yet. Add recursive: true.
2. Deleting a folder that is not empty
// await temp.delete(); // ✗ temp still contains ali.txtFileSystemException: Deletion failed, path = '<temp>/lesson_w7LRLE' (OS Error: Directory not empty, errno = 66)Use delete(recursive: true) — after double-checking the path.
3. Forgetting the separator when joining strings
String folder = 'reports';
print('${folder}ali.txt'); // ✗ no separatorreportsali.txtp.join(folder, 'ali.txt') gives reports/ali.txt.
4. Hard-coding / or \
'reports/ali.txt' happens to work on macOS and Linux. Use p.join or
Platform.pathSeparator so the same code is correct on Windows.
5. Expecting list() to be alphabetical
The order comes from the operating system. Sort the results when order matters.
6. Assuming a relative path is next to your .dart file
A relative path is measured from Directory.current — where the program was started,
not where its source file lives.
Best Practices
- Experiment inside
Directory.systemTemp.createTemp(...), and delete it when done. - Build paths with
p.join; read their parts withp.basename,p.extension,p.dirname. - Use
create(recursive: true)when parent folders might be missing. - Treat
delete(recursive: true)like a power tool: only on paths you built yourself. - Sort listings before showing them to people.
- Prefer the async methods in programs that do other work;
Syncversions are fine for short scripts.
Mini Challenge
Inside a temporary folder, create backup/2026 and backup/2025 in one go each, put a file
notes.txt into backup/2026, then print every entry under backup, sorted, as a path
relative to the temporary folder.
Your program should print exactly:
backup/2025
backup/2026
backup/2026/notes.txtTry it yourself before opening the solution.
Show solution
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
Directory temp = Directory.systemTemp.createTempSync('backup_');
String backup = p.join(temp.path, 'backup');
Directory(p.join(backup, '2026')).createSync(recursive: true);
Directory(p.join(backup, '2025')).createSync(recursive: true);
File(p.join(backup, '2026', 'notes.txt')).writeAsStringSync('hi');
List<String> paths = Directory(backup)
.listSync(recursive: true)
.map((entity) => p.relative(entity.path, from: temp.path))
.toList();
paths.sort();
paths.forEach(print);
temp.deleteSync(recursive: true);
}backup/2025
backup/2026
backup/2026/notes.txtp.relative strips the random temporary folder from the front, so the output is the same on
every run.
Practice Questions
Work through these in order — they run from beginner to application to challenge. Use a temporary folder whenever you create something.
Beginner
- Print whether
Directory.currentis an absolute path. - Print whether
File('marks.txt')is an absolute path. - Use
p.jointo buildschool/class1/ali.txt, then print its basename and its extension.
Application
- Create a temporary folder
quiz_, createa/b/cinside it in one call, print whethercexists, then clean up. - Write
ali.txt,sara.txtandzayan.txtinto a temporary folder and printCount: 3usinglistSync(). - In a temporary folder, create a file
marks.txtand a folderold. Print each entry, sorted, asFile: marks.txtorFolder: old. - Print the
FileSystemEntity.typeof a path that does not exist. - Put a file in a temporary folder, then call
delete()withoutrecursiveinsidetry/catch. OnFileSystemException, printNot empty — deleting everythingand delete recursively. Finally print whether the folder exists.
Challenge
- In a temporary folder, create
a.txt,b.logandsub/c.txt. Count all.txtfiles, including those in sub-folders, and printText files: 2. - Write
Future<int> folderSize(Directory dir)that adds upawait file.length()(the size in bytes) for every file, recursively. Test it withali.txtholdingAli: 80andsub/sara.txtholdingSara: 92.
Practice Answers
Question 1 — Solution
import 'dart:io';
void main() {
print(Directory.current.isAbsolute);
}trueQuestion 2 — Solution
import 'dart:io';
void main() {
print(File('marks.txt').isAbsolute);
}falseQuestion 3 — Solution
import 'package:path/path.dart' as p;
void main() {
String path = p.join('school', 'class1', 'ali.txt');
print(p.basename(path));
print(p.extension(path));
}ali.txt
.txtQuestion 4 — Solution
import 'dart:io';
import 'package:path/path.dart' as p;
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('quiz_');
Directory c = Directory(p.join(temp.path, 'a', 'b', 'c'));
await c.create(recursive: true);
print(await c.exists());
await temp.delete(recursive: true);
}trueQuestion 5 — Solution
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
Directory temp = Directory.systemTemp.createTempSync('count_');
for (String name in ['ali', 'sara', 'zayan']) {
File(p.join(temp.path, '$name.txt')).writeAsStringSync(name);
}
print('Count: ${temp.listSync().length}');
temp.deleteSync(recursive: true);
}Count: 3Question 6 — Solution
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
Directory temp = Directory.systemTemp.createTempSync('kinds_');
File(p.join(temp.path, 'marks.txt')).writeAsStringSync('80');
Directory(p.join(temp.path, 'old')).createSync();
List<FileSystemEntity> entries = temp.listSync();
entries.sort((a, b) => a.path.compareTo(b.path));
for (FileSystemEntity entity in entries) {
String label = entity is File ? 'File' : 'Folder';
print('$label: ${p.basename(entity.path)}');
}
temp.deleteSync(recursive: true);
}File: marks.txt
Folder: oldQuestion 7 — Solution
import 'dart:io';
Future<void> main() async {
print(await FileSystemEntity.type('no_such_folder'));
}notFoundQuestion 8 — Solution
import 'dart:io';
import 'package:path/path.dart' as p;
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('full_');
await File(p.join(temp.path, 'ali.txt')).writeAsString('Ali: 80');
try {
await temp.delete();
} on FileSystemException {
print('Not empty — deleting everything');
await temp.delete(recursive: true);
}
print(await temp.exists());
}Not empty — deleting everything
falseQuestion 9 — Solution
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
Directory temp = Directory.systemTemp.createTempSync('txt_');
Directory(p.join(temp.path, 'sub')).createSync();
for (String name in ['a.txt', 'b.log', p.join('sub', 'c.txt')]) {
File(p.join(temp.path, name)).writeAsStringSync('x');
}
int count = temp
.listSync(recursive: true)
.whereType<File>()
.where((file) => p.extension(file.path) == '.txt')
.length;
print('Text files: $count');
temp.deleteSync(recursive: true);
}Text files: 2Question 10 — Solution
import 'dart:io';
import 'package:path/path.dart' as p;
Future<int> folderSize(Directory dir) async {
int total = 0;
await for (FileSystemEntity entity in dir.list(recursive: true)) {
if (entity is File) {
total += await entity.length();
}
}
return total;
}
Future<void> main() async {
Directory temp = await Directory.systemTemp.createTemp('size_');
await Directory(p.join(temp.path, 'sub')).create();
await File(p.join(temp.path, 'ali.txt')).writeAsString('Ali: 80');
await File(p.join(temp.path, 'sub', 'sara.txt')).writeAsString('Sara: 92');
print('Size: ${await folderSize(temp)} bytes');
await temp.delete(recursive: true);
}Size: 15 bytes'Ali: 80' is 7 bytes and 'Sara: 92' is 8, so the total is 15.
Quick Revision
- What is the difference between a relative and an absolute path?
- What does
Directory.currenttell you? - What does
Platform.pathSeparatorreturn on Windows? - Why practise inside
Directory.systemTemp.createTemp? - What does
recursive: truechange forcreate,listanddelete? - Is the order of
list()guaranteed? - How do you tell a
Filefrom aDirectoryin a listing? - Why is
p.joinsafer than gluing strings?
Key Takeaways
- A path says where something is; a directory holds files and other directories.
- Relative paths are measured from
Directory.current, the folder the program started in. Platform.pathSeparatoris/on macOS and Linux and\on Windows;p.joinhandles it for you.Directory.systemTemp.createTemp('name_')gives a safe, unique playground folder.create(recursive: true)builds missing parents; plaincreate()fails without them.list()streams entries,listSync()returns a list; neither promises an order.File,DirectoryandLinkare allFileSystemEntity; test withis.delete()fails on a non-empty folder;delete(recursive: true)removes everything.
What's Next?
Lecture 108 — JSON. Our files so far hold plain text like Ali: 80. Next we will store
structured data — names, marks and lists together — in JSON, the format almost every app and
web service uses, and turn it into Dart maps and lists with dart:convert.