On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Why Split a Program?
- 2. Your First Library File
- 3. Paths Are Relative to the Importing File
- 4. Public and Private Names
- 5. Imports Are Not Passed Along
- 6. export — One Entry File
- 7. show and hide on export
- 8. The Optional library Directive
- 9. part and part of (Recognize Only)
- 10. Organising a Small Project
- 11. Circular Imports
- 12. 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 102 — Creating Your Own Libraries
| Category | 17 — Libraries & Packages |
| Lecture | 102 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
One long file mixes marks, shopping and bank code until nothing is easy to find. Splitting it into your own libraries gives each job its own file — and
exportjoins them back behind a single import.
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 files shown on Dart 3.12.2. The first line of each code block names its file; run the program with
dart run main.dartfrom the folder that holdsmain.dart.
Prerequisites
Students should already understand, from Lectures 044–101:
reduceandfoldon a list (Lecture 044)- privacy with
_is per file (Lecture 057) - that every
.dartfile is a library, andimportwithas,showandhide(Lecture 101)
Connection With Previous Lectures
In Lecture 101 we imported Ali's and Sara's helper files without asking how they were made.
Meanwhile Ali's school program has grown to one 400-line main.dart: mark averages, a shopping
cart and a bank account, all mixed together. To change the grade rule he scrolls past the
cart code; one stray _ name from the bank part can collide with the marks part.
He wants each job in its own file, each file hiding its helpers, and main.dart reaching all
of it through one import line.
Learning Objectives
By the end of this lecture you will be able to:
- Split a program into several files and connect them with relative imports
- Write correct relative paths, including subfolders and
../ - Decide what a file exposes with public names and
_private names - Explain why an import is not passed on to the files that import you
- Build a single entry file with
export, usingshowandhide - Recognize the optional
library;directive and the discouragedpart/part of - Organise a small project with a
src/folder - Recognize that circular imports work, and why they signal a design problem
1. Why Split a Program?
A file that does one job is easier to read, test and change. Because every Dart file is a
library (Lecture 101), creating your own library is simply creating a new .dart file and
importing it.
Analogy — a kitchen with labelled drawers. Spoons in one, knives in another. You still cook in one kitchen, but you find things instantly.
2. Your First Library File
Move the marks code into marks.dart:
// marks.dart
double average(List<int> marks) => marks.reduce((a, b) => a + b) / marks.length;
String grade(double average) => average >= 80 ? 'A' : 'B';// main.dart
import 'marks.dart';
void main() {
var avg = average([80, 75, 90]);
print('Average: ${avg.toStringAsFixed(1)}');
print('Grade: ${grade(avg)}');
}Average: 81.7
Grade: Amarks.dart has no main — it is not a program, just a library that main.dart uses.
3. Paths Are Relative to the Importing File
The path in import 'marks.dart'; is measured from the file that contains the import, not
from where you typed dart run. Use / for folders and ../ to go up one level:
school_app/
├── main.dart import 'src/grades.dart';
├── src/
│ └── grades.dart
└── tools/
└── check.dart import '../src/grades.dart';Always write /, even on Windows. A backslash is not a folder separator in a Dart string, so
'src\grades.dart' looks for a file called srcgrades.dart.
4. Public and Private Names
Every name without _ is public: importing files can use it. A name starting with _ is
private to its file (Lecture 057). That is how a library hides its helpers:
// report.dart
String report(int marks) => '${_label()}: $marks';
String _label() => 'Marks';// main.dart
import 'report.dart';
void main() {
print(report(72));
// print(_label()); // ✗
}Marks: 72Uncomment the second line and Dart answers:
main.dart:6:9: Error: Method not found: '_label'.
print(_label());
^^^^^^The public names are the library's API — the promise it makes to other files. Private names can change freely without breaking anyone.
5. Imports Are Not Passed Along
src/report.dart imports grades.dart, and main.dart imports src/report.dart:
// src/grades.dart
String grade(int marks) => marks >= 80 ? 'A' : 'B';// src/report.dart
import 'grades.dart';
String report(String name, int marks) => '$name: ${grade(marks)}';// main.dart
import 'src/report.dart';
void main() {
print(report('Ali', 85));
print(grade(85));
}main.dart:6:9: Error: Method not found: 'grade'.
print(grade(85));
^^^^^An import is private to the file that writes it. report.dart can use grade, but it
does not hand grade on to main.dart.
6. export — One Entry File
export does hand names on. A file that only exports others is an entry file. Keep the
files from step 5 and add two more:
// school.dart
export 'src/grades.dart';
export 'src/report.dart';
export 'src/shop.dart';// src/shop.dart
int total(List<int> prices) => prices.fold(0, (sum, p) => sum + p);// main.dart
import 'school.dart';
void main() {
print(report('Ali', 85));
print(grade(62));
print(total([500, 50, 1500]));
}Ali: A
B
2050One import now reaches three files. If src/ is later split differently, only school.dart
changes — main.dart does not notice.
7. show and hide on export
export accepts the same show and hide as import (Lecture 101), so the entry file
decides exactly what the outside world gets:
// school.dart
export 'src/grades.dart';
export 'src/shop.dart' show total;// main.dart
import 'school.dart';
void main() {
print(grade(62));
print(total([500, 50]));
}B
550Any other public name in shop.dart stays inside the project.
8. The Optional library Directive
You may see a file start with library;. It marks the file as a library on purpose, and it is
the place for documentation about the whole library:
// school.dart
library;
export 'src/grades.dart';
export 'src/shop.dart' show total;It changes nothing — the file was already a library. Older code writes a name,
library school;; that also works, but modern Dart leaves the name out.
9. part and part of (Recognize Only)
Older code can stitch one library together from several files:
// shop.dart
part 'shop_prices.dart';
int total() => _prices.fold(0, (sum, p) => sum + p);// shop_prices.dart
part of 'shop.dart';
final _prices = [500, 50, 1500];main.dart imports only shop.dart; total() prints 2050. A part is not a library of its
own, so _prices is shared and the part cannot be imported by itself. The Dart team
recommends small libraries with import and export instead. You will still meet part in
code generators, which we will not use in this course.
10. Organising a Small Project
A common layout puts the entry file at the top and the real work in src/:
school_app/
├── main.dart
├── school.dart ← entry file: only exports
└── src/
├── grades.dart
├── report.dart
└── shop.dartThe rule: outside code imports school.dart, never a file inside src/. Dart does not
enforce this — it is a convention — but it keeps one door into your code. In
Lecture 103 — Dart Packages this grows into the standard layout: a lib/ folder with
lib/src/ inside it, imported with package: paths.
11. Circular Imports
Two files may import each other. A teacher marks students, and a student greets teachers:
// student.dart
import 'teacher.dart';
class Student {
final String name;
Student(this.name);
String greet(Teacher t) => '$name greets ${t.name}';
}// teacher.dart
import 'student.dart';
class Teacher {
final String name;
Teacher(this.name);
String mark(Student s) => '${s.name} gets an A from $name';
}// main.dart
import 'student.dart';
import 'teacher.dart';
void main() {
var ali = Student('Ali');
var sara = Teacher('Sara');
print(ali.greet(sara));
print(sara.mark(ali));
}Ali greets Sara
Ali gets an A from SaraDart allows it, and dart analyze reports no issues. But a cycle means neither file can be
understood or reused alone. If two files need each other, they usually belong in one file,
or the shared part belongs in a third file that both import.
12. A Real-World Example
A small school library: marks with a private rounding helper, a bank account with a private balance, and one entry file.
// src/marks.dart
double average(List<int> marks) =>
_round(marks.reduce((a, b) => a + b) / marks.length);
double _round(double value) => (value * 10).round() / 10;// src/bank_account.dart
class BankAccount {
final String owner;
int _balance = 0;
BankAccount(this.owner);
int get balance => _balance;
void deposit(int amount) => _balance += amount;
}// school.dart
library;
export 'src/bank_account.dart';
export 'src/marks.dart';// main.dart
import 'school.dart';
void main() {
print('Sara average: ${average([88, 95, 79])}');
var account = BankAccount('Zayan');
account.deposit(1500);
account.deposit(250);
print('${account.owner}: ${account.balance}');
}Sara average: 87.3
Zayan: 1750main.dart sees average, BankAccount and balance, but not _round or _balance.
Code Examples
Example 1 — Two Files
// greeting.dart
String greet(String name) => 'Hello, $name!';// main.dart
import 'greeting.dart';
void main() {
print(greet('Zayan'));
}Hello, Zayan!Example 2 — A Constant and a Class
// student.dart
const passMark = 50;
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
bool get passed => marks >= passMark;
}// main.dart
import 'student.dart';
void main() {
var ali = Student('Ali', 45);
print('Pass mark: $passMark');
print('${ali.name} passed: ${ali.passed}');
}Pass mark: 50
Ali passed: falseExample 3 — A File in a Subfolder
// src/convert.dart
double toFahrenheit(double celsius) => celsius * 9 / 5 + 32;// main.dart
import 'src/convert.dart';
void main() {
print(toFahrenheit(30));
}86.0Example 4 — Going Up with ../
This uses src/convert.dart from Example 3.
// tools/check.dart
import '../src/convert.dart';
void main() {
print(toFahrenheit(0));
}Run dart run tools/check.dart:
32.0Example 5 — A Private Helper
// price.dart
int withTax(int price) => price + _tax(price);
int _tax(int price) => price ~/ 10;// main.dart
import 'price.dart';
void main() {
print(withTax(500));
}550Example 6 — An Entry File with hide
// src/score.dart
int addBonus(int score) => score + 5;
String debugDump(int score) => 'score=$score';// game.dart
export 'src/score.dart' hide debugDump;// main.dart
import 'game.dart';
void main() {
print(addBonus(90));
}95Example 7 — Student Marks
// src/marks.dart
int highest(List<int> marks) => marks.reduce((a, b) => a > b ? a : b);
int lowest(List<int> marks) => marks.reduce((a, b) => a < b ? a : b);// main.dart
import 'src/marks.dart';
void main() {
var marks = [55, 91, 78, 66];
print('Highest: ${highest(marks)}');
print('Lowest: ${lowest(marks)}');
}Highest: 91
Lowest: 55Example 8 — Shopping Cart
// src/cart.dart
class Cart {
final _prices = <int>[];
void add(int price) => _prices.add(price);
int get total => _prices.fold(0, (sum, p) => sum + p);
}// main.dart
import 'src/cart.dart';
void main() {
var cart = Cart();
cart.add(500);
cart.add(1500);
print('Total: ${cart.total}');
}Total: 2000How it works
_pricesis private, somain.dartcan only change the list throughadd.totalis a public getter that adds the prices withfold.
Example 9 — Bank Library with an Entry File
// src/account.dart
class Account {
int _balance = 0;
int get balance => _balance;
void deposit(int amount) => _balance += amount;
}// bank.dart
export 'src/account.dart';// main.dart
import 'bank.dart';
void main() {
var account = Account();
account.deposit(1000);
print('Balance: ${account.balance}');
}Balance: 1000Example 10 — Weather Library with show
// src/temperature.dart
double average(List<double> temps) =>
temps.reduce((a, b) => a + b) / temps.length;
String feel(double temp) => temp >= 30 ? 'Hot' : 'Pleasant';// weather.dart
export 'src/temperature.dart' show average, feel;// main.dart
import 'weather.dart';
void main() {
var avg = average([29.5, 31.0, 32.5]);
print('$avg — ${feel(avg)}');
}31.0 — HotCommon Beginner Mistakes
1. A wrong path
// import 'mark.dart'; // ✗ the file is marks.dartError: Error when reading 'mark.dart': No such file or directory2. Forgetting ../
From tools/check.dart, import 'src/convert.dart'; looks for tools/src/convert.dart. Write
'../src/convert.dart'.
3. Expecting an import to pass through
If main.dart imports report.dart, it does not get what report.dart imports — you saw
Error: Method not found: 'grade'. in step 5. Import the file directly, or export it.
4. Expecting export to import
// school.dart
export 'src/grades.dart';
// String best(int marks) => 'Best: ${grade(marks)}'; // ✗Error: Method not found: 'grade'.export only passes names on. To use grade inside school.dart too, add
import 'src/grades.dart'; as well.
5. Writing export after code
String hello() => 'hi';
// export 'src/grades.dart'; // ✗Error: Directives must appear before any declarations.import, export, library and part all go at the top.
6. Importing a part file
// import 'shop_prices.dart'; // ✗Error: Can't import 'shop_prices.dart', because it has a 'part of' declaration.Best Practices
- Give each file one job and a name that says it:
marks.dart,bank_account.dart. - Keep helpers private with
_; make public only what other files need. - Offer one entry file that exports the public API; keep the work in
src/. - Use
showonexportwhen only a few names should leave the library. - Prefer
import/exportoverpart/part of. - Avoid circular imports; move shared code into a third file.
- Use
/in paths and keep file nameslower_case_with_underscores.
Mini Challenge
Build shop.dart as an entry file over src/cart.dart (a subtotal function) and
src/discount.dart (a public applyDiscount and a private _rate of 10%). main.dart
imports only shop.dart and prints exactly:
Subtotal: 2000
To pay: 1800Try it yourself before opening the solution.
Show solution
// src/cart.dart
int subtotal(List<int> prices) => prices.fold(0, (sum, p) => sum + p);// src/discount.dart
const _rate = 10;
int applyDiscount(int amount) => amount - amount * _rate ~/ 100;// shop.dart
export 'src/cart.dart';
export 'src/discount.dart';// main.dart
import 'shop.dart';
void main() {
var sum = subtotal([500, 1500]);
print('Subtotal: $sum');
print('To pay: ${applyDiscount(sum)}');
}Subtotal: 2000
To pay: 1800How it works
shop.dartexports both files, so one import reachessubtotalandapplyDiscount._ratestays private todiscount.dart;main.dartcannot see or change it.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Create
hello.dartwith asayHi(String name)function, and call it frommain.dart. - What import line does
main.dartneed for a file atsrc/tools.dart? - What import line does
tools/run.dartneed for that samesrc/tools.dart?
Application
- In
bonus.dart, write a publicfinalMarks(int marks)that adds a private_bonusof5. PrintfinalMarks(70)frommain.dart. main.dartimportsa.dart, anda.dartimportsb.dart. Canmain.dartcall a function fromb.dart? Why?- Write an entry file
school.dartthat exportssrc/marks.dartandsrc/shop.dart. - Change your entry file so only
totalleavessrc/shop.dart. - Is
library;required at the top ofschool.dart? What does it add?
Challenge
- Build
temperature.dartwith aThermometerclass: a private list of readings, anaddmethod and anaveragegetter. Add29.5,31.0and30.5frommain.dartand printAverage: 30.3. student.dartandteacher.dartimport each other. Does the program run? How would you restructure it?
Practice Answers
Question 1 — Solution
// hello.dart
void sayHi(String name) => print('Hi, $name!');// main.dart
import 'hello.dart';
void main() {
sayHi('Sara');
}Hi, Sara!Question 2 — Solution
import 'src/tools.dart';
Question 3 — Solution
import '../src/tools.dart'; — ../ goes up from tools/ first.
Question 4 — Solution
// bonus.dart
const _bonus = 5;
int finalMarks(int marks) => marks + _bonus;// main.dart
import 'bonus.dart';
void main() {
print(finalMarks(70));
}75Question 5 — Solution
No. An import is private to the file that writes it, so the call fails with
Error: Method not found:. main.dart must import b.dart itself, or a.dart must
export 'b.dart';.
Question 6 — Solution
// school.dart
export 'src/marks.dart';
export 'src/shop.dart';Question 7 — Solution
// school.dart
export 'src/marks.dart';
export 'src/shop.dart' show total;Question 8 — Solution
No. Every file is already a library. library; only states it on purpose and gives a place
for documentation about the whole library.
Question 9 — Solution
// temperature.dart
class Thermometer {
final _readings = <double>[];
void add(double reading) => _readings.add(reading);
double get average => _readings.reduce((a, b) => a + b) / _readings.length;
}// main.dart
import 'temperature.dart';
void main() {
var thermometer = Thermometer();
thermometer.add(29.5);
thermometer.add(31.0);
thermometer.add(30.5);
print('Average: ${thermometer.average.toStringAsFixed(1)}');
}Average: 30.3Question 10 — Solution
Yes — Dart allows circular imports, as step 11 showed. It is still a design smell: neither file stands alone. Put both classes in one file, or move what they share into a third file that both import.
Quick Revision
- How do you create your own library in Dart?
- A relative import path is measured from where?
- Which names can another file use?
- Does an import pass its names on to the files that import you?
- What does
exportdo? - What is an entry file?
- Is
library;required? - Why is
part/part ofdiscouraged today? - Are circular imports allowed? Are they a good idea?
Key Takeaways
- Every
.dartfile is a library; a new file is a new library. - Relative paths start from the importing file; use
/and../. - Public names form the library's API;
_names stay inside the file. - Imports are not passed on;
exportpasses names on, with optionalshowandhide. - An entry file that only exports keeps one door into a
src/folder. library;is optional;part/part ofstill works but is discouraged.- Circular imports work, but they are a design smell.
What's Next?
Lecture 103 — Dart Packages. Our libraries live in one folder and are imported with
relative paths. Next we turn a folder into a package — with lib/, lib/src/ and
package: imports — and learn how to use code that other people have published.