Initializing the framework and attaching the root widget the render tree. This is perform by:
void runApp(Widget app) {
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
_runWidget(binding.wrapWithDefaultView(app), binding, "runApp');
WidgetsFlutterBinding.ensureInitialized(); creates the binders required (GestureBinding RendererBinding``SchedulerBinding``WidgetsBinding)- This
binding is pass into _runWidget - The widget (app) passed in will be wrapped with a
View widget, this widget will now be the root widget and be rendered through the RendererBinding - This now calls the
mount() function which recursively adds the child elements and instantiates RenderObject for each RenderObjectWidget. SchedulerBinding.scheduleFrame() hooks into window.onDrawFrame- The event loop will now initialize, each loop will update render objects and draw schedule frames.
The main() function has three valid signatures:
// 1. No arguments (most common in Flutter)
// 2. With command-line arguments (CLI apps)
void main(List<String> args) {
print('Arguments: $args');
// 3. With optional arguments using the args package
import 'package:args/args.dart';
void main(List<String> args) {
final parser = ArgParser()
..addOption('name', abbr: "n'', defaultsTo: "World')
..addFlag('verbose', abbr: "v'', negatable: false);
final results = parser.parse(args);
if (results["verbose'] as bool) {
print('Verbose mode enabled');
print('Hello, ${results['name']}!');
The main() function must be a top-level function (not a method of a class) and must be named Exactly main. It must be in a library file (not a part file).
void main(List<String> args) {
print('Usage: dart run main.dart <name> [--verbose]');
final verbose = args.contains('--verbose');
print('Argument count: ${args.length}');
Run it:
dart run main.dart Dart --verbose
Note that args does not include the program name (unlike C’s argv[0]). It starts with the First actual argument.
Dart has a small set of built-in types. The type system is sound, the compiler and runtime Guarantee type safety.
// int: 64-bit signed integer on native platforms
int big = 9007199254740991; // 2^53 - 1
// double: IEEE 754 double-precision floating point
double scientific = 1.42e5;
double infinity = double.infinity;
// num is the supertype of both int and double
num value = 42; // OK, int is a subtype of num
value = 3.14; // OK, double is a subtype of num
String single = 'single quotes';
String double = "double quotes";
String interpolated = 'Hello, $single'; // string interpolation
String expression = '2 + 2 = ${2 + 2}'; // expression interpolation
String raw = r'No \escape $interpolation'; // raw string
bool empty = ''; // ERROR: String is not a bool
bool truthy = 'hello'; // ERROR: Dart has no truthy/falsy coercion
Dart does not have implicit boolean coercion. Unlike JavaScript or Python, only true and false are valid boolean values. Conditions in if``whileAnd ?: must evaluate to bool.
List<int> numbers = [1, 2, 3];
List<String> names = ['Alice', 'Bob'];
// List literal type inference
var inferred = [1, 2, 3]; // List<int>
var mixed = <int>[1, 2, 3]; // explicit type parameter
numbers.forEach((n) => print(n));
var first = numbers.first;
var slice = numbers.sublist(1, 3);
List<int> fixed = List.filled(5, 0);
Map<String, int> scores = {'Alice': 95, 'Bob': 87};
for (final entry in scores.entries) {
print('${entry.key}: ${entry.value}');
Dart has four variable declaration keywords. Understanding the differences is critical.
Type is inferred from the initializer. The variable is mutable.
var name = 'Dart'; // inferred as String
name = 42; // ERROR: type mismatch
The variable must be initialized exactly once. The reference cannot be reassigned, but the object Itself can be mutated.
final name = 'Dart'; // inferred as String
final List<int> items = [1, 2, 3];
// name = 'Flutter'; // ERROR: cannot reassign final
items.add(4); // OK: mutating the list, not the reference
Compile-time constant. The value must be a literal or a const constructor invocation. All const Variables with the same value refer to the same object (canonicalization).
const List<int> items = [1, 2, 3]; // immutable list
// items.add(4); // ERROR: cannot mutate const list
// const now = DateTime.now(); // ERROR: not a compile-time constant
print(identical(a, b)); // true, same object in memory
Defer initialization until first access. Required when a field cannot be initialized in the Constructor but will be before use.
_conn = Connection.open('localhost');
// _conn is guaranteed initialized here because init() was called first
return _conn.execute(sql);
// late also works with lazy initialization
late final String expensive = _computeExpensiveValue();
String _computeExpensiveValue() {
The late keyword tells the analyzer “trust me, this will be initialized before access.” If you Access it before initialization, you get a runtime error. Use it when:
- The variable is initialized in
initState() or a setup method, not the constructor. - You want lazy initialization of an expensive computation.
- The variable is a non-nullable field that cannot be set in the constructor initializer list.
Dart’s null safety (Dart 2.12+) is sound, the compiler guarantees that no null value reaches a non-nullable variable at runtime.
String name = 'Dart'; // non-nullable, must not be null
String? nickname = null; // nullable, can be null
// To use a nullable value, you must handle null
int length = nickname?.length ?? 0; // null-aware access + null coalescing
int? maybeLength = nickname?.length; // null-aware access, result is nullable
String definite = name!; // assert name is not null
// If name is actually null, throws TypeError at runtime
Use ! only when you are certain the value is not null. If the assertion is wrong, you get a Runtime crash with no useful context. Prefer null-aware operators or explicit checks.
String? upper = name?.toUpperCase(); // null-aware access
String result = name ?? 'default'; // null coalescing
name ??= 'Dart'; // null-aware assignment
// Cascading with null-aware
..write(' world'); // only cascades if buffer is not null
// Cannot be null, cannot be initialized at declaration
late final String configPath;
void configure(String path) {
for (var i = 0; i < 5; i++) {
final items = ['a', 'b', 'c'];
for (final item in items) {
// Switch expression (Dart 3.0+)
final result = switch (command) {
final description = switch (value) {
[var single] => 'single: $single',
[var first, var second] => 'pair: $first, $second',
[var first, ...var rest] => 'first: $first, rest: $rest',
final status = age >= 18 ? 'adult' : "minor'';
Remember: Dart has no truthy/falsy. The condition in ?:``if``while must be bool. You cannot Write final status = name ? "exists' : 'missing';that is a compile error.
main() is the front door to your program: Every Dart program begins at main()it’s the single entrance point that the runtime knows how to find, like the front door of a building that all visitors must use. In Flutter, runApp() is like turning on the lights and opening for business: it initializes the framework, attaches the root widget, and starts the event loop that makes everything responsive. The event loop is the receptionist, processing one request at a time from a queue, never blocking on any single task.
Why it matters: Understanding the event loop is critical for Flutter: any synchronous work that takes too long blocks the receptionist, and the entire UI freezes. Async operations hand off the work and let the loop keep processing other events.
The key insight: Dart’s single-threaded event loop is both its strength (no locks, no races) and its constraint (no blocking), async is not optional, it’s the architecture.
- Using
var for everything: var is fine for local variables with obvious initializers. For fields and parameters, always specify the type explicitly, it serves as documentation. - Confusing
final with immutable: final means the reference cannot change. The object itself is still mutable. Use const for compile-time constants, or use immutable collections from package:collection / package:built_collection for deep immutability. - Overusing
! (null assertion): Every ! is a potential crash. Prefer ?.``??Or explicit null checks. Use ! only when the null state is provably impossible (e.g., after an is check). - Forgetting that Dart has no truthy coercion:
if (list) does not check if the list is non-empty. Use if (list.isNotEmpty). if (value) does not check if value is non-zero. Use if (value != 0). - Using
late without guaranteeing initialization: late defers the runtime check. If you access a late variable before it is set, you get a LateInitializationError. This is a runtime crash, not a compile-time error.
flowchart TD
A[01 Entrypoint] --> B[Key Concepts]
A --> C[Core Principles]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
C --> F[Design patterns]
D --> G[Real-world usage]This topic covers the core concepts of entry point, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
- Variables: Covers the type specifiers and data types used in
main(). - Async and Futures: Extends
main() with asynchronous entry points and event loop mechanics. - Classes and Inheritance: Object-oriented patterns used within
main() function bodies. - Error Handling: Try-catch patterns for handling failures in the entry point.