Libraries and Code Organization
Prerequisites: Packages and pub, Getters, setters, and privacy
As a project grows, a clear structure keeps it manageable.
Libraries
In Dart, every file is a library. Names starting with _ are private
to their library; everything else is public. Import other libraries to
use their public names:
// lib/src/cart.dart
class Cart { /* ... */ }
class _PriceCache { /* private to this file */ }
// bin/shop.dart
import 'package:shop/src/cart.dart';
void main() {
final cart = Cart();
}
Imports within your own package use the package:your_name/... form (or
relative paths — pick one style and stick to it).
The standard package layout
shop/
├── bin/shop.dart # executable entry points
├── lib/
│ ├── shop.dart # the public API: exports what others may use
│ └── src/ # implementation files
│ ├── cart.dart
│ └── pricing.dart
├── test/ # tests mirroring lib/
└── pubspec.yaml
Code under lib/src/ is treated as private to the package by
convention. The top-level lib/shop.dart chooses what's public:
// lib/shop.dart
export 'src/cart.dart' show Cart;
export 'src/pricing.dart';
Organize by feature
In apps — especially Flutter apps — group code by feature rather than
by kind: features/cart/, features/checkout/, and features/profile/,
each with its own models, logic, and widgets, scale better than one giant
models/ folder and one giant widgets/ folder.
Lints and formatting
Configure lint rules in analysis_options.yaml, starting from the
recommended set:
include: package:lints/recommended.yaml
linter:
rules:
- prefer_final_locals
- unawaited_futures
Run dart format . and dart analyze in your editor and in CI, so style
never has to be debated in code review.
Common mistakes
- Very large files mixing many classes and responsibilities. Split by responsibility early.
- Importing
src/files of other packages — they're private implementation details and can change without notice.