On this page27 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Why Organize and Document?
- 2. The Standard Package Layout
- 3. The Public Entry File
- 4. lib/src/ — The Storeroom
- 5. bin/, example/ and test/
- 6. README and CHANGELOG
- 7. Feature Folders vs Layer Folders
- 8. One Class per File?
- 9. Doc Comments: The First Sentence
- 10. [References] in Square Brackets
- 11. Code Samples in Doc Comments
- 12. Documenting Parameters in Prose
- 13. Generating HTML with dart doc
- 14. The public_member_api_docs Lint
- 15. 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 125 — Code Organization & Documentation
| Category | 21 — Professional Dart Development |
| Lecture | 125 of 140 |
| Estimated video length | 17 min |
| Target | Dart 3.12.2 |
| Level | Advanced |
Clean, immutable code is still hard to use if nobody can find it or tell what it does. A standard package layout shows where everything lives, and doc comments turn into a browsable website with
dart doc.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every message in this lecture is real. Each one was produced by running
dart run,dart analyze,dart testanddart docon the code shown, on Dart 3.12.2. Long folder paths indart docoutput are shortened to…/grade_book.
Prerequisites
Students should already understand, from Lectures 001–124:
///doc comments (Lecture 003) and the Documentation guide of Effective Dart (Lecture 117)exportwithshow, and an entry file over asrc/folder (Lecture 102)- packages,
package:imports anddart create -t package(Lectures 103, 113) dart analyze, lint rules andanalysis_options.yaml(Lectures 114, 117)- tests in
test/(Lectures 119–121)
Connection With Previous Lectures
Lectures 123 and 124 made our code clean and safe. But Ali's grade code is now one long
file, and Sara wants to use it.
Which names may she call? What does addMark(120) do? Today she must read every line
to find out.
Learning Objectives
By the end of this lecture you will be able to:
- Describe the standard folders of a Dart package:
lib/,lib/src/,bin/,test/,example/ - Expose a package's public surface from one entry file with
export - Choose between feature folders and layer folders
- Decide how many classes belong in one file
- Write doc comments with a one-sentence summary,
[references]and code samples - Document parameters in prose instead of tags
- Generate an HTML documentation site with
dart doc - Require docs on every public member with the
public_member_api_docslint
1. Why Organize and Document?
Organization answers where code lives; documentation answers what it does without opening it.
Analogy — a shop has a counter and a storeroom. Customers see the counter and the price labels; the storeroom is for staff.
lib/with its entry file is the counter,lib/src/is the storeroom, and doc comments are the labels.
2. The Standard Package Layout
Lecture 113 showed the package template. Let's build a real package on it:
dart create -t package grade_bookCreating grade_book using template package...
.gitignore
analysis_options.yaml
CHANGELOG.md
pubspec.yaml
README.md
example/grade_book_example.dart
lib/grade_book.dart
lib/src/grade_book_base.dart
test/grade_book_test.dartWe will delete grade_book_base.dart and grow it into this:
grade_book/
├── bin/grade_book.dart ← a program you can run
├── example/grade_book_example.dart ← how to use the package
├── lib/
│ ├── grade_book.dart ← the public entry file
│ └── src/ ← the real code, private by convention
│ ├── grading.dart
│ ├── report_card.dart
│ └── student.dart
├── test/grade_book_test.dart
├── CHANGELOG.md
├── README.md
└── pubspec.yamlOther projects import only package:grade_book/grade_book.dart.
3. The Public Entry File
lib/grade_book.dart holds no logic. It documents the library and exports the public
surface — exactly the names users may call:
/// Tools for recording student marks and printing report cards.
///
/// Create a [Student], add marks with [Student.addMark], then pass the
/// student to a [ReportCard] to print a summary.
library;
export 'src/grading.dart' show gradeFor;
export 'src/report_card.dart';
export 'src/student.dart';show gradeFor keeps formatMark, a helper in the same file, out of the public surface
(Lecture 102). library; gives the comment above it something to attach to.
4. lib/src/ — The Storeroom
Each src/ file holds one topic — lib/src/student.dart:
/// A student and the marks they have earned.
class Student {
/// Creates a student called [name] with no marks yet.
Student(this.name);
/// The student's name, such as `'Ali'`.
final String name;
final List<int> _marks = [];
/// The marks added so far, in the order they were added.
List<int> get marks => List.unmodifiable(_marks);
/// The average of [marks], or `0` when there are none.
double get average =>
_marks.isEmpty ? 0 : _marks.reduce((a, b) => a + b) / _marks.length;
/// Adds [mark] to this student's marks.
///
/// The [mark] must be from 0 to 100; any other value throws an
/// [ArgumentError] and leaves [marks] unchanged.
void addMark(int mark) {
if (mark < 0 || mark > 100) {
throw ArgumentError.value(mark, 'mark', 'must be from 0 to 100');
}
_marks.add(mark);
}
}Files inside src/ import each other with relative paths — report_card.dart starts with
import 'grading.dart';. Everything outside lib/ uses package: imports (Lecture 103).
5. bin/, example/ and test/
All three import only the entry file. example/grade_book_example.dart:
import 'package:grade_book/grade_book.dart';
void main() {
final ali = Student('Ali')
..addMark(80)
..addMark(75)
..addMark(85);
print(ReportCard(ali).render());
}Student: Ali
Average: 80.0
Grade: Abin/grade_book.dart reads arguments (Lecture 113); dart run grade_book runs it:
import 'package:grade_book/grade_book.dart';
void main(List<String> args) {
final student = Student(args.first);
for (final mark in args.skip(1)) {
student.addMark(int.parse(mark));
}
print(ReportCard(student).render());
}dart run grade_book Sara 90 70 64Student: Sara
Average: 74.7
Grade: Btest/grade_book_test.dart tests the same public surface, for example
expect(gradeFor(79.9), 'B') (Lecture 120).
6. README and CHANGELOG
README.md says what the package does and shows a short usage sample. pub.dev uses it as
the package's front page, and so does dart doc:
# grade_book
Record student marks and print report cards.
## Usage
```dart
final ali = Student('Ali')..addMark(80);
print(ReportCard(ali).render());
```CHANGELOG.md lists what changed in each version from pubspec.yaml, newest first:
## 1.1.0
- Add `ReportCard`.
## 1.0.0
- Initial version.7. Feature Folders vs Layer Folders
When src/ grows, group its files. Layer folders group by kind of code; feature
folders group by what the code is about:
By layer By feature
lib/src/ lib/src/
├── models/ ├── students/
│ ├── student.dart │ ├── student.dart
│ └── course.dart │ └── student_report.dart
└── reports/ └── courses/
├── student_report.dart ├── course.dart
└── course_report.dart └── course_report.dartLayers suit small packages. As a package grows, prefer features: a change to students touches one folder.
8. One Class per File?
Dart does not require one class per file. Use these guidelines:
- Give each important public class its own file, named after it in
lowercase_with_underscores:ReportCard→report_card.dart. - Keep small helpers next to the class they serve.
- Split a file when its parts change for different reasons.
9. Doc Comments: The First Sentence
Lecture 117 set the rules: ///, a one-sentence summary, names in brackets. The first
sentence matters most, because tools show it alone in lists. Start with a verb for
functions ("Returns…", "Adds…") and a noun phrase for classes and properties ("A student…",
"The average…"). Put more detail in a new paragraph after a blank /// line:
/// Adds [mark] to this student's marks.
///
/// The [mark] must be from 0 to 100; any other value throws an
/// [ArgumentError] and leaves [marks] unchanged.10. [References] in Square Brackets
A name in brackets becomes a link in the generated docs. It can be a parameter
([mark]), a member ([marks]), another class ([ArgumentError]), or a member of another
class ([Student.addMark]). Here is lib/src/report_card.dart:
import 'grading.dart';
import 'student.dart';
/// A printable summary of one [Student]'s results.
class ReportCard {
/// Creates a report card for [student].
ReportCard(this.student);
/// The student this card describes.
final Student student;
/// Returns the card as three lines: name, average and grade.
///
/// The grade comes from [gradeFor].
String render() =>
'Student: ${student.name}\n'
'Average: ${formatMark(student.average)}\n'
'Grade: ${gradeFor(student.average)}';
}11. Code Samples in Doc Comments
One example often explains more than a paragraph. Write it in a fenced block marked dart
inside the comment — lib/src/grading.dart:
/// Returns the letter grade for an [average] mark.
///
/// Averages of 80 and above get `'A'`, 65 and above `'B'`, 50 and above
/// `'C'`, and anything lower `'F'`.
///
/// ```dart
/// print(gradeFor(82.5)); // A
/// print(gradeFor(49.9)); // F
/// ```
String gradeFor(double average) {
if (average >= 80) return 'A';
if (average >= 65) return 'B';
if (average >= 50) return 'C';
return 'F';
}
/// Formats [mark] with one decimal place, such as `80.0`.
String formatMark(double mark) => mark.toStringAsFixed(1);Short values go in single backticks: `'A'`.
12. Documenting Parameters in Prose
Some languages use tags like @param mark. Dart doesn't. Mention each parameter in a
sentence, in brackets, together with its rules:
/// Returns [price] with [percent] percent taken off.
///
/// The [percent] must be from 0 to 100. When [roundDown] is `true`, the
/// result is rounded down to whole rupees.
double discounted(double price, double percent, {bool roundDown = false}) {
final result = price - price * percent / 100;
return roundDown ? result.floorToDouble() : result;
}
void main() {
print(discounted(2000, 15));
print(discounted(1999, 15, roundDown: true));
}1700.0
1699.0Explain what the types cannot: ranges, units and bad input.
13. Generating HTML with dart doc
Run dart doc in the package folder:
dart docDocumenting grade_book...
Discovering libraries...
Linking elements...
Precaching local docs for 138012 elements...
Initialized dartdoc with 53 libraries
Generating docs for library grade_book.dart from package:grade_book/grade_book.dart...
Found 0 warnings and 0 errors.
Documented 1 public library in 3.6 seconds
Success! Docs generated into …/grade_book/doc/apiOpen doc/api/index.html in a browser:
- The front page shows
README.md. - The library page shows the entry file's comment, then Classes (
ReportCard,Student) and Functions (gradeFor), each with its first sentence only. - Class and member pages show full comments, code samples and implementations; every name in brackets is a link.
- A search box sits at the top.
Only what the entry file exports is documented: formatMark has no page, and bin/,
example/ and test/ are left out. doc/api is generated — add it to .gitignore.
pub.dev runs the same tool for published packages.
14. The public_member_api_docs Lint
Forgetting a doc comment is easy. This lint, which is not in recommended, reports every
public member in lib/ without one:
include: package:lints/recommended.yaml
linter:
rules:
- public_member_api_docsRemove the comments above student and render() in report_card.dart:
info - lib/src/report_card.dart:9:17 - Missing documentation for a public member. Try adding documentation for the member. - public_member_api_docs
info - lib/src/report_card.dart:11:10 - Missing documentation for a public member. Try adding documentation for the member. - public_member_api_docs
2 issues found.Private members such as _marks, and code in bin/ and test/, are not checked. Turn it on
for packages others will use; for a small app it is usually too strict.
15. A Real-World Example
Before Ali shares a new version, he runs four commands from the package folder:
dart format .
dart analyze
dart test
dart docFormatted 7 files (0 changed) in 0.01 seconds.
Analyzing grade_book...
No issues found!
00:00 +2: All tests passed!
...
Found 0 warnings and 0 errors.
Documented 1 public library in 3.6 secondsEverything is clean. Sara reads the website, imports one file, and never opens lib/src/.
Code Examples
Example 1 — A One-Sentence Summary
/// Returns the total of all [prices].
int total(List<int> prices) {
var sum = 0;
for (final price in prices) {
sum += price;
}
return sum;
}
void main() {
print(total([500, 50, 1500]));
}2050Example 2 — Documenting a Class and Its Members
/// An item for sale in the shop.
class Product {
/// Creates a product called [name] that costs [price] rupees.
Product(this.name, this.price);
/// The name printed on the receipt.
final String name;
/// The price in whole rupees.
final int price;
}
void main() {
final pen = Product('Pen', 50);
print('${pen.name}: ${pen.price}');
}Pen: 50Example 3 — References Between Names
/// The lowest mark that passes. Used by [isPassing].
const passMark = 50;
/// Whether [mark] is a passing mark.
///
/// A mark passes when it is at least [passMark].
bool isPassing(int mark) => mark >= passMark;
void main() {
print(isPassing(65));
print(isPassing(40));
}true
falseExample 4 — A Code Sample in the Docs
/// Returns [name] with its first letter in upper case.
///
/// ```dart
/// print(capitalize('sara')); // Sara
/// ```
String capitalize(String name) =>
name.isEmpty ? name : '${name[0].toUpperCase()}${name.substring(1)}';
void main() {
print(capitalize('sara'));
print(capitalize('zayan'));
}Sara
ZayanExample 5 — A Library-Level Comment
/// Helpers for printing shop receipts.
///
/// Use [rupees] to format every amount the same way.
library;
/// Formats [amount] as rupees with two decimal places.
String rupees(double amount) => 'Rs ${amount.toStringAsFixed(2)}';
void main() {
print(rupees(1550));
}Rs 1550.00Example 6 — Student Marks
/// Returns the average of [marks], or `0` when the list is empty.
double averageOf(List<int> marks) {
if (marks.isEmpty) return 0;
return marks.reduce((a, b) => a + b) / marks.length;
}
/// Returns `'Pass'` when [average] is at least 50, otherwise `'Fail'`.
///
/// Usually called with the result of [averageOf].
String resultFor(double average) => average >= 50 ? 'Pass' : 'Fail';
void main() {
final marks = [80, 75, 85];
final average = averageOf(marks);
print('Ali: $average (${resultFor(average)})');
}Ali: 80.0 (Pass)Example 7 — Shopping Cart
/// A shopping cart that adds up prices.
class Cart {
final Map<String, int> _items = {};
/// The sum of every item's price times its quantity.
int get total {
var sum = 0;
_items.forEach((name, price) => sum += price);
return sum;
}
/// Adds [quantity] of an item called [name] at [price] rupees each.
///
/// Adding a name that is already in the cart replaces its line.
void add(String name, int price, {int quantity = 1}) {
_items[name] = price * quantity;
}
}
void main() {
final cart = Cart()
..add('Notebook', 500, quantity: 3)
..add('Pen', 50);
print('Total: ${cart.total}');
}Total: 1550Example 8 — Bank Account
/// A bank account that never goes below zero.
class Account {
/// Creates an account for [owner] with an opening [balance].
Account(this.owner, [this._balance = 0]);
/// The name of the person who owns the account.
final String owner;
double _balance;
/// The money in the account right now.
double get balance => _balance;
/// Removes [amount] from the [balance].
///
/// Throws a [StateError] when [amount] is more than the [balance];
/// the balance is then left unchanged.
void withdraw(double amount) {
if (amount > _balance) throw StateError('Not enough money');
_balance -= amount;
}
}
void main() {
final account = Account('Sara', 1000)..withdraw(250.5);
print('${account.owner}: ${account.balance}');
try {
account.withdraw(5000);
} on StateError catch (e) {
print(e.message);
}
}Sara: 749.5
Not enough moneyExample 9 — Piggy Bank in lib/
This is the solution to the Mini Challenge below, so try that first. lib/piggy_bank.dart
in a package named ex, with public_member_api_docs on:
/// A piggy bank that counts the coins dropped into it.
class PiggyBank {
/// Creates an empty piggy bank that belongs to [owner].
PiggyBank(this.owner);
/// The child who owns this piggy bank.
final String owner;
/// The number of rupees saved so far.
int coins = 0;
/// Adds [amount] rupees to [coins].
void drop(int amount) {
coins += amount;
}
}Example 10 — Temperature Package
A second package, weather_kit. lib/weather_kit.dart:
/// Temperature tools for daily weather reports.
library;
export 'src/weather.dart' show dailyReport, toFahrenheit;lib/src/weather.dart:
/// Converts [celsius] to degrees Fahrenheit.
double toFahrenheit(double celsius) => celsius * 9 / 5 + 32;
/// Returns the highest of [readings] for [city] in °C and °F.
///
/// The [readings] are in Celsius and must not be empty.
String dailyReport(String city, List<double> readings) {
final high = readings.reduce((a, b) => a > b ? a : b);
return '$city: ${_oneDecimal(high)} °C / '
'${_oneDecimal(toFahrenheit(high))} °F';
}
String _oneDecimal(double degrees) => degrees.toStringAsFixed(1);bin/weather_kit.dart:
import 'package:weather_kit/weather_kit.dart';
void main() {
print(dailyReport('Lahore', [28, 33, 31]));
print(toFahrenheit(25));
}Lahore: 33.0 °C / 91.4 °F
77.0Common Beginner Mistakes
1. Writing // instead of ///
// The student this card describes.
final Student student; info - lib/src/report_card.dart:10:17 - Missing documentation for a public member. Try adding documentation for the member. - public_member_api_docsA // comment is invisible to dart doc. Doc comments need three slashes.
2. Misspelling a reference
/// A printable summary of one [Studnet]'s results. warning: unresolved doc reference [Studnet]
from report_card.ReportCard: (file:///…/grade_book/lib/src/report_card.dart:5:7)
Found 1 warning and 0 errors.dart doc still builds the site, but the link is dead. Read its warnings.
3. A library comment without library;
info - lib/grade_book.dart:1:1 - Dangling library doc comment. Add a 'library' directive after the library comment. - dangling_library_doc_commentsA comment above export lines belongs to nothing. Put library; right under it.
4. Importing another package's src/ files
import 'package:grade_book/src/grading.dart';This compiles and formatMark works, but it is undocumented and may change in any version.
Import only the entry file.
5. Comments that repeat the code
/// The name. above final String name; adds nothing. Say what the reader cannot see:
/// The student's name, such as 'Ali'.
Best Practices
- Use the standard layout and export a deliberate public surface with
showorhide. - Prefer feature folders once
src/grows; name files after their main class. - Start every doc comment with a one-sentence summary; add detail in a new paragraph.
- Document what is thrown, allowed ranges and units; add samples to anything non-obvious.
- Keep
README.mdandCHANGELOG.mdcurrent; fix everydart docwarning. - Turn on
public_member_api_docsfor packages others use.
Mini Challenge
Zayan's package has this undocumented lib/piggy_bank.dart, and public_member_api_docs
is on:
class PiggyBank {
PiggyBank(this.owner);
final String owner;
int coins = 0;
void drop(int amount) {
coins += amount;
}
} info - lib/piggy_bank.dart:1:7 - Missing documentation for a public member. Try adding documentation for the member. - public_member_api_docs…and four more lines like it, at 2:3, 4:16, 5:7 and 7:8.
Document it so dart analyze prints No issues found!.
Try it yourself before opening the solution.
Show solution
Use the documented class from Example 9. With it, this program in bin/piggy.dart:
import 'package:ex/piggy_bank.dart';
void main() {
final bank = PiggyBank('Zayan')
..drop(150)
..drop(50);
print('${bank.owner}: ${bank.coins}');
}Zayan: 200Each of the five flagged names — class, constructor, owner, coins, drop — now has a
one-sentence summary, so dart analyze reports No issues found!.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Which folder holds the package's entry file, and which holds code that is private by convention?
- Turn
// Adds two numbers.aboveint add(int a, int b) => a + b;into a proper doc comment with references. Calladd(80, 75). - Which command generates HTML docs, where does it put them, and which file opens the site?
Application
-
Write a one-sentence doc comment for
double celsiusToFahrenheit(double celsius)and printcelsiusToFahrenheit(30). -
A package
exhaslib/src/cart.dartwithcartTotalandlib/src/format.dartwithformatPriceandroundPrice. Writelib/shop.dartso users getcartTotalandformatPrice, but notroundPrice. -
Sort these into feature folders:
student.dart,fee.dart,student_report.dart,fee_receipt.dart. -
Rewrite this comment the Dart way:
/// Records a mark. /// @param subject the subject /// @param mark the mark, 0-100 void recordMark(String subject, int mark) {} -
Add a code sample to the doc comment of
bool isPassing(int mark) => mark >= 50;.
Challenge
-
With
public_member_api_docson, which lines of thislib/file are flagged?/// A student's result in one exam. class ExamResult { ExamResult(this.subject, this.mark); final String subject; final int mark; /// Whether the result counts as a pass. bool get passed => _isValid() && mark >= 50; bool _isValid() => mark >= 0 && mark <= 100; } -
Plan a package
bank_kitwith anAccountclass, aformatMoneyhelper users may call and aroundCentshelper they may not. Draw its folder tree and write its entry file.
Practice Answers
Question 1 — Solution
lib/ holds the entry file (lib/grade_book.dart); lib/src/ holds the private code.
Question 2 — Solution
/// Returns the sum of [a] and [b].
int add(int a, int b) => a + b;
void main() {
print(add(80, 75));
}155Question 3 — Solution
dart doc writes the site to doc/api; open doc/api/index.html.
Question 4 — Solution
/// Converts [celsius] to degrees Fahrenheit.
double celsiusToFahrenheit(double celsius) => celsius * 9 / 5 + 32;
void main() {
print(celsiusToFahrenheit(30));
}86.0Question 5 — Solution
/// Tools for building shopping carts and printing prices.
library;
export 'src/cart.dart';
export 'src/format.dart' show formatPrice;With print(formatPrice(cartTotal([500, 50, 1500]))); the output is:
Rs 2050Calling roundPrice(2054) fails:
Error: Method not found: 'roundPrice'.Question 6 — Solution
lib/src/
├── students/
│ ├── student.dart
│ └── student_report.dart
└── fees/
├── fee.dart
└── fee_receipt.dartQuestion 7 — Solution
/// Records [mark] for the subject called [subject].
///
/// The [mark] must be from 0 to 100.
void recordMark(String subject, int mark) {
print('$subject: $mark');
}
void main() {
recordMark('Math', 80);
}Math: 80Question 8 — Solution
/// Whether [mark] is at least 50.
///
/// ```dart
/// print(isPassing(65)); // true
/// print(isPassing(40)); // false
/// ```
bool isPassing(int mark) => mark >= 50;
void main() {
print(isPassing(65));
print(isPassing(40));
}true
falseQuestion 9 — Solution
Three: the constructor (3:3), subject (5:16) and mark (6:13). The class and
passed are documented, and _isValid is private.
Question 10 — Solution
bank_kit/
├── lib/
│ ├── bank_kit.dart
│ └── src/
│ ├── account.dart
│ └── money.dart ← formatMoney and roundCents
├── test/bank_kit_test.dart
└── pubspec.yaml/// Bank accounts and money formatting.
library;
export 'src/account.dart';
export 'src/money.dart' show formatMoney;Quick Revision
- What do
lib/,lib/src/,bin/,example/andtest/hold? - When do feature folders beat layer folders?
- Why does the first sentence of a doc comment matter most?
- How do you document a parameter in Dart?
- What does
dart docleave out of the site? - What does
public_member_api_docscheck, and where?
Key Takeaways
- A package keeps its entry file in
lib/, its code inlib/src/, and programs, examples and tests inbin/,example/andtest/. - The entry file exports the public surface;
showandhidekeep helpers private. - Prefer feature folders as code grows; give important classes their own file.
- Doc comments:
///, a one-sentence summary,[references], code samples, and parameters described in prose. README.mdis the front page;CHANGELOG.mdrecords each version.dart docbuilds a searchable site indoc/apifrom the exported names only.public_member_api_docsflags every undocumented public member inlib/.
What's Next?
Lecture 126 — Performance & Memory. Our code is now organized and documented, but is it fast? Next we will measure how long code takes, see where memory goes, and learn which small choices make Dart programs quicker and lighter.