Dart Program Structure
Dart Program Structure
Every programming language has a structure — a set of rules about how files are organized and how different pieces fit together. Understanding Dart's program structure from the start will prevent confusion later and help you read and write code with confidence.
In this lesson, we'll dissect the anatomy of a Dart file from top to bottom.
The Big Picture
A complete Dart file can contain these elements, generally in this order:
1. Library declaration (optional)
2. Import statements
3. Top-level variables
4. Top-level functions
5. Classes / Enums / Mixins / Extensions
6. main() function (entry point)
You don't need all of these in every file. The only requirement for a runnable program is a main() function. Let's explore each section.
Section 1: Import Statements
Imports bring in code from other libraries — either Dart's built-in libraries, packages from pub.dev, or other files in your project.
Dart Core Library Imports
dartimport 'dart:math'; // Math functions: sin, cos, Random, etc. import 'dart:io'; // File I/O, stdin, stdout import 'dart:convert'; // JSON encoding/decoding import 'dart:async'; // Future, Stream, async utilities import 'dart:collection'; // Queue, LinkedHashMap, etc.
Important: dart:core is the only library that is auto-imported. It provides print(), String, int, List, Map, Object, Exception, and many other fundamental types. You never need to write import 'dart:core';.
Package Imports (from pub.dev)
After running dart pub get, import packages like:
dartimport 'package:http/http.dart' as http; import 'package:path/path.dart';
Relative File Imports
Import other Dart files in your own project:
dartimport 'utils/string_helpers.dart'; import '../models/user.dart';
Import with Aliases (as)
Prevent naming conflicts using as:
dartimport 'dart:math' as math; void main() { print(math.pi); // 3.141592653589793 print(math.sqrt(16)); // 4.0 }
Import with Show / Hide
Control exactly what gets imported:
dart// Only import Random from dart:math import 'dart:math' show Random; // Import everything EXCEPT pi import 'dart:math' hide pi;
Section 2: Top-Level Variables
Variables declared outside of any class or function are called top-level variables. They exist at the file scope and are accessible everywhere in the file.
dart// Top-level constant — known at compile time const String appName = 'MyApp'; // Top-level final — set once at runtime final String version = '1.0.0'; // Top-level mutable variable int launchCount = 0;
When to Use Top-Level Variables
- Constants that apply to the whole file or program
- Configuration values that don't change
- Avoid mutable top-level variables in large programs (prefer encapsulation in classes)
dart// Good use of top-level constants const double pi = 3.14159265358979; const int maxRetries = 3; const String baseUrl = 'https://api.example.com';
Section 3: Top-Level Functions
Functions defined outside of any class are top-level functions. main() is the most important one, but you can define as many as you need.
dart// Top-level utility function String greet(String name) { return 'Hello, $name!'; } // Top-level calculation function int square(int n) => n * n; // Arrow function shorthand (single expression) bool isEven(int n) => n % 2 == 0; void main() { print(greet('Alice')); // Hello, Alice! print(square(5)); // 25 print(isEven(4)); // true }
Arrow functions (=>) are shorthand for single-expression functions. int square(int n) => n * n; is identical to writing a full return statement inside curly braces.
Section 4: Classes
Classes are blueprints for objects — they bundle data (fields) and behavior (methods) together. Dart is fully object-oriented.
dart// Class definition class Person { // Fields (instance variables) String name; int age; // Constructor Person(this.name, this.age); // Named constructor Person.anonymous() : name = 'Unknown', age = 0; // Instance method void introduce() { print('Hi, I\'m $name and I\'m $age years old.'); } // Getter bool get isAdult => age >= 18; // Override toString String toString() => 'Person($name, $age)'; }
Enums
Enums define a fixed set of named values:
dartenum Direction { north, south, east, west } enum Status { active, inactive, pending; // Enums can have methods in Dart 3 String get label => name.toUpperCase(); }
Section 5: The main() Function
As covered in the previous lesson, main() is the entry point — where Dart begins execution. It must be a top-level function.
dartvoid main() { // Program starts here } // OR with command-line arguments: void main(List<String> args) { print('Arguments: $args'); }
There is exactly one main() per runnable Dart program.
Section 6: Comments
Comments are notes for humans — the Dart compiler ignores them completely. Good comments explain why something is done, not what is done.
Single-Line Comments
dart// This is a single-line comment int x = 42; // Comments can appear after code too
Multi-Line Comments
dart/* This is a multi-line comment. It can span as many lines as needed. Useful for temporarily disabling blocks of code. */ int y = 10;
Documentation Comments (///)
Triple-slash comments generate API documentation with dart doc:
dart/// Calculates the area of a circle. /// /// Takes the [radius] as input and returns the area as a [double]. /// Throws an [ArgumentError] if [radius] is negative. /// /// Example: /// ```dart /// double area = circleArea(5.0); /// print(area); // 78.53981633974483 /// ``` double circleArea(double radius) { if (radius < 0) throw ArgumentError('Radius cannot be negative'); return 3.14159265358979 * radius * radius; }
When you hover over circleArea() in VS Code, you'll see this documentation rendered beautifully.
Convention: Use /// for public APIs (functions, classes, properties that others will use). Use // for internal implementation notes.
File Naming Conventions
Dart has clear, community-enforced naming rules for files:
| Convention | Example |
|---|---|
| snake_case for file names | my_helper.dart, string_utils.dart |
| PascalCase for class names | class MyHelper {} |
| camelCase for variables/functions | myVariable, calculateTotal() |
| SCREAMING_SNAKE_CASE for constants | const MAX_SIZE = 100; (or camelCase const) |
| lowercase for packages | package:my_package |
✅ user_profile.dart
✅ http_client.dart
✅ main.dart
❌ UserProfile.dart
❌ httpClient.dart
❌ Main.Dart
Single-File vs Multi-File Programs
Single-File Program
Suitable for scripts, exercises, and simple tools:
dart// everything in one file: main.dart const String appVersion = '1.0.0'; class Calculator { int add(int a, int b) => a + b; int subtract(int a, int b) => a - b; } void printBanner() { print('=== Calculator v$appVersion ==='); } void main() { printBanner(); var calc = Calculator(); print(calc.add(10, 5)); // 15 print(calc.subtract(10, 5)); // 5 }
Multi-File Program
For larger projects, split code across files:
lib/
models/
user.dart
product.dart
services/
auth_service.dart
api_service.dart
utils/
string_utils.dart
date_utils.dart
bin/
main.dart
bin/main.dart:
dartimport '../lib/models/user.dart'; import '../lib/services/auth_service.dart'; import '../lib/utils/string_utils.dart'; void main() { var user = User(name: 'Alice', email: 'alice@example.com'); print(user); }
Putting It All Together — Full Example Program
Here is a complete Dart file showcasing every structural element we discussed:
dart// ============================================================ // FILE: life_calculator.dart // DESCRIPTION: Demonstrates full Dart program structure // ============================================================ // --- IMPORTS --- import 'dart:math' show Random; // --- TOP-LEVEL CONSTANT --- const String appName = 'Life Calculator'; const int currentYear = 2024; // --- ENUM --- enum LifeStage { child, teenager, adult, senior; String get label { switch (this) { case LifeStage.child: return 'Child (0-12)'; case LifeStage.teenager: return 'Teenager (13-17)'; case LifeStage.adult: return 'Adult (18-64)'; case LifeStage.senior: return 'Senior (65+)'; } } } // --- CLASS --- /// Represents a person and provides life-related calculations. class Person { /// The person's full name. final String name; /// The person's birth year. final int birthYear; /// Creates a [Person] with the given [name] and [birthYear]. Person(this.name, this.birthYear); /// The person's current age. int get age => currentYear - birthYear; /// The person's current life stage. LifeStage get lifeStage { if (age <= 12) return LifeStage.child; if (age <= 17) return LifeStage.teenager; if (age <= 64) return LifeStage.adult; return LifeStage.senior; } String toString() => '$name (born $birthYear, age $age)'; } // --- TOP-LEVEL FUNCTIONS --- /// Generates a random birth year between [minAge] and [maxAge]. int randomBirthYear(int minAge, int maxAge) { final random = Random(); final age = minAge + random.nextInt(maxAge - minAge); return currentYear - age; } /// Prints a formatted summary for a [Person]. void printPersonSummary(Person person) { print('Name: ${person.name}'); print('Birth Year: ${person.birthYear}'); print('Age: ${person.age}'); print('Life Stage: ${person.lifeStage.label}'); print('---'); } // --- MAIN FUNCTION (Entry Point) --- void main() { print('=== $appName ===\n'); /* Create a list of people with different ages */ final people = [ Person('Alice', 1990), Person('Bob', 2010), Person('Charlie', 1955), Person('Diana', randomBirthYear(13, 17)), ]; // Print summary for each person for (final person in people) { printPersonSummary(person); } print('Total people: ${people.length}'); }
Output:
=== Life Calculator ===
Name: Alice
Birth Year: 1990
Age: 34
Life Stage: Adult (18-64)
---
Name: Bob
Birth Year: 2010
Age: 14
Life Stage: Teenager (13-17)
---
Name: Charlie
Birth Year: 1955
Age: 69
Life Stage: Senior (65+)
---
Name: Diana
Birth Year: (random)
Age: (random)
Life Stage: Teenager (13-17)
---
Total people: 4
Dart Program Structure — Cheat Sheet
┌──────────────────────────────────────────────┐
│ DART FILE STRUCTURE │
├──────────────────────────────────────────────┤
│ // library declaration (optional) │
│ library my_library; │
├──────────────────────────────────────────────┤
│ // imports │
│ import 'dart:math'; │
│ import 'package:http/http.dart'; │
│ import 'other_file.dart'; │
├──────────────────────────────────────────────┤
│ // top-level variables │
│ const String APP_NAME = 'App'; │
│ int counter = 0; │
├──────────────────────────────────────────────┤
│ // enums, classes, mixins, extensions │
│ enum Color { red, green, blue } │
│ class MyClass { ... } │
├──────────────────────────────────────────────┤
│ // top-level functions │
│ void helper() { ... } │
│ int calculate(int x) => x * 2; │
├──────────────────────────────────────────────┤
│ // entry point │
│ void main() { ... } │
└──────────────────────────────────────────────┘
Summary
In this lesson, you learned the complete anatomy of a Dart file:
| Element | Purpose |
|---|---|
| Imports | Bring in external libraries (dart:, package:, relative) |
dart:core | Auto-imported — provides String, int, print(), etc. |
| Top-level variables | File-scoped constants and shared state |
| Top-level functions | Reusable logic outside of any class |
| Classes / Enums | Blueprints for objects; named sets of values |
main() | The entry point — execution begins here |
// | Single-line comment |
/* */ | Multi-line comment |
/// | Documentation comment (generates API docs) |
| File naming | snake_case for files, PascalCase for classes |
Next Up: Now that you understand how a Dart program is structured, we'll dive into variables and data types — the building blocks of every Dart program.