Watch the video (10 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Why Split a Program?
  5. 2. Your First Library File
  6. 3. Paths Are Relative to the Importing File
  7. 4. Public and Private Names
  8. 5. Imports Are Not Passed Along
  9. 6. export — One Entry File
  10. 7. show and hide on export
  11. 8. The Optional library Directive
  12. 9. part and part of (Recognize Only)
  13. 10. Organising a Small Project
  14. 11. Circular Imports
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 102 — Creating Your Own Libraries

Category17 — Libraries & Packages
Lecture102 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelIntermediate

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 export joins 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.dart from the folder that holds main.dart.


Prerequisites

Students should already understand, from Lectures 044–101:

  • reduce and fold on a list (Lecture 044)
  • privacy with _ is per file (Lecture 057)
  • that every .dart file is a library, and import with as, show and hide (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, using show and hide
  • Recognize the optional library; directive and the discouraged part / 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:

dart
// marks.dart
double average(List<int> marks) => marks.reduce((a, b) => a + b) / marks.length;

String grade(double average) => average >= 80 ? 'A' : 'B';
dart
// main.dart
import 'marks.dart';

void main() {
  var avg = average([80, 75, 90]);
  print('Average: ${avg.toStringAsFixed(1)}');
  print('Grade: ${grade(avg)}');
}
output
Average: 81.7
Grade: A

marks.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:

output
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:

dart
// report.dart
String report(int marks) => '${_label()}: $marks';

String _label() => 'Marks';
dart
// main.dart
import 'report.dart';

void main() {
  print(report(72));
  // print(_label()); // ✗
}
output
Marks: 72

Uncomment the second line and Dart answers:

output
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:

dart
// src/grades.dart
String grade(int marks) => marks >= 80 ? 'A' : 'B';
dart
// src/report.dart
import 'grades.dart';

String report(String name, int marks) => '$name: ${grade(marks)}';
dart
// main.dart
import 'src/report.dart';

void main() {
  print(report('Ali', 85));
  print(grade(85));
}
output
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:

dart
// school.dart
export 'src/grades.dart';
export 'src/report.dart';
export 'src/shop.dart';
dart
// src/shop.dart
int total(List<int> prices) => prices.fold(0, (sum, p) => sum + p);
dart
// main.dart
import 'school.dart';

void main() {
  print(report('Ali', 85));
  print(grade(62));
  print(total([500, 50, 1500]));
}
output
Ali: A
B
2050

One 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:

dart
// school.dart
export 'src/grades.dart';
export 'src/shop.dart' show total;
dart
// main.dart
import 'school.dart';

void main() {
  print(grade(62));
  print(total([500, 50]));
}
output
B
550

Any 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:

dart
// 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:

dart
// shop.dart
part 'shop_prices.dart';

int total() => _prices.fold(0, (sum, p) => sum + p);
dart
// 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/:

output
school_app/
├── main.dart
├── school.dart        ← entry file: only exports
└── src/
    ├── grades.dart
    ├── report.dart
    └── shop.dart

The 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:

dart
// student.dart
import 'teacher.dart';

class Student {
  final String name;
  Student(this.name);

  String greet(Teacher t) => '$name greets ${t.name}';
}
dart
// teacher.dart
import 'student.dart';

class Teacher {
  final String name;
  Teacher(this.name);

  String mark(Student s) => '${s.name} gets an A from $name';
}
dart
// 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));
}
output
Ali greets Sara
Ali gets an A from Sara

Dart 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.

dart
// 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;
dart
// 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;
}
dart
// school.dart
library;

export 'src/bank_account.dart';
export 'src/marks.dart';
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}');
}
output
Sara average: 87.3
Zayan: 1750

main.dart sees average, BankAccount and balance, but not _round or _balance.


Code Examples

Example 1 — Two Files

dart
// greeting.dart
String greet(String name) => 'Hello, $name!';
dart
// main.dart
import 'greeting.dart';

void main() {
  print(greet('Zayan'));
}
output
Hello, Zayan!

Example 2 — A Constant and a Class

dart
// student.dart
const passMark = 50;

class Student {
  final String name;
  final int marks;
  Student(this.name, this.marks);

  bool get passed => marks >= passMark;
}
dart
// main.dart
import 'student.dart';

void main() {
  var ali = Student('Ali', 45);
  print('Pass mark: $passMark');
  print('${ali.name} passed: ${ali.passed}');
}
output
Pass mark: 50
Ali passed: false

Example 3 — A File in a Subfolder

dart
// src/convert.dart
double toFahrenheit(double celsius) => celsius * 9 / 5 + 32;
dart
// main.dart
import 'src/convert.dart';

void main() {
  print(toFahrenheit(30));
}
output
86.0

Example 4 — Going Up with ../

This uses src/convert.dart from Example 3.

dart
// tools/check.dart
import '../src/convert.dart';

void main() {
  print(toFahrenheit(0));
}

Run dart run tools/check.dart:

output
32.0

Example 5 — A Private Helper

dart
// price.dart
int withTax(int price) => price + _tax(price);

int _tax(int price) => price ~/ 10;
dart
// main.dart
import 'price.dart';

void main() {
  print(withTax(500));
}
output
550

Example 6 — An Entry File with hide

dart
// src/score.dart
int addBonus(int score) => score + 5;

String debugDump(int score) => 'score=$score';
dart
// game.dart
export 'src/score.dart' hide debugDump;
dart
// main.dart
import 'game.dart';

void main() {
  print(addBonus(90));
}
output
95

Example 7 — Student Marks

dart
// 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);
dart
// main.dart
import 'src/marks.dart';

void main() {
  var marks = [55, 91, 78, 66];
  print('Highest: ${highest(marks)}');
  print('Lowest: ${lowest(marks)}');
}
output
Highest: 91
Lowest: 55

Example 8 — Shopping Cart

dart
// 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);
}
dart
// main.dart
import 'src/cart.dart';

void main() {
  var cart = Cart();
  cart.add(500);
  cart.add(1500);
  print('Total: ${cart.total}');
}
output
Total: 2000

How it works

  1. _prices is private, so main.dart can only change the list through add.
  2. total is a public getter that adds the prices with fold.

Example 9 — Bank Library with an Entry File

dart
// src/account.dart
class Account {
  int _balance = 0;

  int get balance => _balance;

  void deposit(int amount) => _balance += amount;
}
dart
// bank.dart
export 'src/account.dart';
dart
// main.dart
import 'bank.dart';

void main() {
  var account = Account();
  account.deposit(1000);
  print('Balance: ${account.balance}');
}
output
Balance: 1000

Example 10 — Weather Library with show

dart
// src/temperature.dart
double average(List<double> temps) =>
    temps.reduce((a, b) => a + b) / temps.length;

String feel(double temp) => temp >= 30 ? 'Hot' : 'Pleasant';
dart
// weather.dart
export 'src/temperature.dart' show average, feel;
dart
// main.dart
import 'weather.dart';

void main() {
  var avg = average([29.5, 31.0, 32.5]);
  print('$avg — ${feel(avg)}');
}
output
31.0 — Hot

Common Beginner Mistakes

1. A wrong path

dart
// import 'mark.dart'; // ✗ the file is marks.dart
output
Error: Error when reading 'mark.dart': No such file or directory

2. 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

dart
// school.dart
export 'src/grades.dart';

// String best(int marks) => 'Best: ${grade(marks)}'; // ✗
output
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

dart
String hello() => 'hi';

// export 'src/grades.dart'; // ✗
output
Error: Directives must appear before any declarations.

import, export, library and part all go at the top.

6. Importing a part file

dart
// import 'shop_prices.dart'; // ✗
output
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 show on export when only a few names should leave the library.
  • Prefer import/export over part/part of.
  • Avoid circular imports; move shared code into a third file.
  • Use / in paths and keep file names lower_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:

output
Subtotal: 2000
To pay: 1800

Try it yourself before opening the solution.

Show solution
dart
// src/cart.dart
int subtotal(List<int> prices) => prices.fold(0, (sum, p) => sum + p);
dart
// src/discount.dart
const _rate = 10;

int applyDiscount(int amount) => amount - amount * _rate ~/ 100;
dart
// shop.dart
export 'src/cart.dart';
export 'src/discount.dart';
dart
// main.dart
import 'shop.dart';

void main() {
  var sum = subtotal([500, 1500]);
  print('Subtotal: $sum');
  print('To pay: ${applyDiscount(sum)}');
}
output
Subtotal: 2000
To pay: 1800

How it works

  1. shop.dart exports both files, so one import reaches subtotal and applyDiscount.
  2. _rate stays private to discount.dart; main.dart cannot see or change it.

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Create hello.dart with a sayHi(String name) function, and call it from main.dart.
  2. What import line does main.dart need for a file at src/tools.dart?
  3. What import line does tools/run.dart need for that same src/tools.dart?

Application

  1. In bonus.dart, write a public finalMarks(int marks) that adds a private _bonus of 5. Print finalMarks(70) from main.dart.
  2. main.dart imports a.dart, and a.dart imports b.dart. Can main.dart call a function from b.dart? Why?
  3. Write an entry file school.dart that exports src/marks.dart and src/shop.dart.
  4. Change your entry file so only total leaves src/shop.dart.
  5. Is library; required at the top of school.dart? What does it add?

Challenge

  1. Build temperature.dart with a Thermometer class: a private list of readings, an add method and an average getter. Add 29.5, 31.0 and 30.5 from main.dart and print Average: 30.3.
  2. student.dart and teacher.dart import each other. Does the program run? How would you restructure it?

Practice Answers

Question 1 — Solution

dart
// hello.dart
void sayHi(String name) => print('Hi, $name!');
dart
// main.dart
import 'hello.dart';

void main() {
  sayHi('Sara');
}
output
Hi, Sara!

Question 2 — Solution

import 'src/tools.dart';

Question 3 — Solution

import '../src/tools.dart'; — ../ goes up from tools/ first.

Question 4 — Solution

dart
// bonus.dart
const _bonus = 5;

int finalMarks(int marks) => marks + _bonus;
dart
// main.dart
import 'bonus.dart';

void main() {
  print(finalMarks(70));
}
output
75

Question 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

dart
// school.dart
export 'src/marks.dart';
export 'src/shop.dart';

Question 7 — Solution

dart
// 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

dart
// 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;
}
dart
// 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)}');
}
output
Average: 30.3

Question 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

  1. How do you create your own library in Dart?
  2. A relative import path is measured from where?
  3. Which names can another file use?
  4. Does an import pass its names on to the files that import you?
  5. What does export do?
  6. What is an entry file?
  7. Is library; required?
  8. Why is part / part of discouraged today?
  9. Are circular imports allowed? Are they a good idea?

Key Takeaways

  • Every .dart file 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; export passes names on, with optional show and hide.
  • An entry file that only exports keeps one door into a src/ folder.
  • library; is optional; part / part of still 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.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.