Introduction to Dart Programming
Flutter - Introduction to Dart Programming (with Null Safety)
Section titled “Flutter - Introduction to Dart Programming (with Null Safety)”Flutter applications are written using the Dart programming language. Dart is a modern, object-oriented, client-optimized language developed by Google. It’s designed for building fast apps on any platform (mobile, web, desktop, server). Understanding Dart basics is essential for Flutter development.
This chapter covers fundamental Dart concepts relevant to Flutter, including Null Safety, a major feature introduced to help prevent null reference errors.
Basic Dart Program Structure
Section titled “Basic Dart Program Structure”Every Dart app must have a top-level main() function, which serves as the entry point.
// This is a single-line comment.
/* This is a multi-line comment.*/
// The main entry point of the application.void main() { // print() displays output to the console. print('Hello, Dart!');}Variables and Data Types
Section titled “Variables and Data Types”Variables store references to values. Dart is type-safe, meaning variable types are checked at compile time (or runtime if using dynamic).
Declaring Variables
Section titled “Declaring Variables”-
Type Annotation: Explicitly declare the type:
-
var: Dart infers the type based on the initial value. Once inferred, the type cannot change. -
dynamic: Allows the variable type to change at runtime. Use sparingly, as it sacrifices type safety.
Null Safety
Section titled “Null Safety”Dart’s sound null safety means that variables cannot hold null unless you explicitly allow them to. This significantly reduces runtime null errors.
-
Non-Nullable (Default): Variables must be initialized before use and cannot be assigned
null. -
Nullable (
?): Add a?after the type to indicate that a variable can benull. -
Null-aware Operators:
-
?.(Conditional Access): Access a property/method only if the object isn’t null, otherwise returns null.message?.length
-
??(If Null Operator): Provide a default value if the expression on the left is null.String name = nullableName ?? 'Guest';
-
??=(Null-aware Assignment): Assign a value only if the variable is currently null.count ??= 0;
-
Assertion Operator (
!): Use!to tell Dart you are certain an expression won’t be null at runtime. Use with caution; if it is null, it throws an exception. -
int len = nullableString!.length; // Throws if nullableString is null -
lateKeyword: Used for non-nullable variables initialized after declaration. Useful for instance variables initialized ininitStatein Flutter. -
late String description; -
- If accessed before initialization, it throws an error.
-
- Can be combined with
final:late final String apiData;
- Can be combined with
Constants (final and const)
Section titled “Constants (final and const)”-
final: The variable can only be set once. Its value is determined at runtime. -
const: Marks the variable as a compile-time constant. The value must be known at compile time.constobjects are deeply immutable and canonicalized (a single instance is created and reused).
Built-in Types
Section titled “Built-in Types”- Numbers:
int(arbitrary-size integers),double(64-bit floating-point). Both are subtypes ofnum. - Strings: Sequence of UTF-16 code units. Use single (
'...') or double ("...") quotes. Supports string interpolation ($or${expression}). - Booleans:
boolwith valuestrueandfalse. - Lists (Arrays): Ordered collection of objects.
List<int> numbers = [1, 2, 3]; - Sets: Unordered collection of unique items.
Set<String> fruits = {'apple', 'banana', 'apple'}; // {'apple', 'banana'} - Maps (Dictionaries): Collection of key-value pairs. Keys must be unique.
Map<String, int> scores = {'Alice': 90, 'Bob': 85}; - Runes: Represent Unicode code points.
- Symbols: Opaque identifiers used for reflection (less common in Flutter apps).
String s1 = 'Single quotes work.';String s2 = "Double quotes also work.";String s3 = 'It\'s easy to escape.';
int value = 10;String interpolated = 'The value is $value.'; // The value is 10.String calculated = 'Value times two is ${value * 2}.'; // Value times two is 20.
List<String> names = ['Ana', 'Bob', 'Charlie'];print(names[0]); // Access by index: Ananames.add('David');
Map<String, String> capitals = { 'USA': 'Washington D.C.', 'UK': 'London'};print(capitals['UK']); // Access by key: LondonFunctions
Section titled “Functions”Functions are first-class objects in Dart (can be assigned to variables, passed as arguments).
// Basic function with type annotationsint add(int a, int b) { return a + b;}
// Shorthand syntax for single-expression functionsdouble multiply(double x, double y) => x * y;
// Functions can have optional parameters// Positional optional parameters (use [])String say(String from, String msg, [String? device]) { var result = '$from says $msg'; if (device != null) { result = '$result with a $device'; } return result;}// Call: say('Bob', 'Hi') or say('Bob', 'Hi', 'smartphone')
// Named optional parameters (use {})void enableFlags({bool? bold, bool? hidden}) { print('Bold: ${bold ?? false}, Hidden: ${hidden ?? false}');}// Call: enableFlags(bold: true) or enableFlags(hidden: false, bold: true)
// Named required parameters (use 'required' keyword)void printUser({required String name, int? age}) { print('User: $name, Age: ${age ?? 'Unknown'}');}// Call: printUser(name: 'Alice') or printUser(name: 'Bob', age: 30)
void main() { print(add(5, 3)); // 8 print(multiply(4.0, 2.5)); // 10.0
print(say('Alice', 'Hello')); // Alice says Hello enableFlags(hidden: true);
// Anonymous function (closure) var numbers = [1, 2, 3]; numbers.forEach((number) { print('Number: $number'); });}Control Flow Statements
Section titled “Control Flow Statements”Dart supports standard control flow:
if/elseforloops (includingfor-in)whileanddo-whileloopsbreakandcontinueswitch/case(Dart’s switch is powerful, supporting arbitrary objects and requiringbreakor other exit statements)assert(Used during development to disrupt execution if a boolean condition is false. Disabled in production code.)
void processScore(int score) { if (score >= 90) { print('Grade A'); } else if (score >= 80) { print('Grade B'); } else { print('Grade C or lower'); }
var list = [10, 20, 30]; for (var item in list) { print(item); }
for (int i = 0; i < 3; i++) { print('Index $i'); }
// Assert example (only works in debug mode) // assert(score > 0, 'Score must be positive');}Object-Oriented Programming (OOP)
Section titled “Object-Oriented Programming (OOP)”Dart is object-oriented with classes and mixin-based inheritance.
- Classes: Blueprints for objects. Use the
classkeyword. - Constructors: Special functions for creating instances (objects) of a class. Can be unnamed, named (
ClassName.namedConstructor()), or factory (factory ClassName(...)). - Fields (Instance Variables): Variables within a class.
- Methods: Functions within a class.
- Inheritance: Use
extendsto inherit from another class. Use@overrideannotation when overriding a superclass member. - Mixins: A way to reuse code across multiple class hierarchies without deep inheritance. Use the
mixinkeyword to define andwithto apply. - Interfaces: Dart doesn’t have a specific
interfacekeyword. Every class implicitly defines an interface. Useimplementsto guarantee a class provides the interface’s members.
class Vehicle { String model; int year;
// Constructor (shorthand for assigning fields) Vehicle(this.model, this.year);
void honk() { print('Beep beep!'); }
@override String toString() => '$year $model';}
class Car extends Vehicle { int numberOfDoors;
// Call superclass constructor Car(String model, int year, this.numberOfDoors) : super(model, year);
// Override method @override void honk() { print('Honk honk!'); super.honk(); // Optionally call superclass method }}
// Mixin examplemixin Electric { void charge() { print('Charging...'); }}
// Class using a mixinclass ElectricCar extends Car with Electric { ElectricCar(String model, int year) : super(model, year, 4);}
void main() { var myCar = Car('Toyota Camry', 2022, 4); print(myCar); // 2022 Toyota Camry myCar.honk(); // Honk honk! Beep beep!
var myElectricCar = ElectricCar('Tesla Model 3', 2023); myElectricCar.charge(); // Charging...}Asynchronous Programming (async, await, Future)
Section titled “Asynchronous Programming (async, await, Future)”Dart uses Future objects to represent values that will be available in the future, and async/await keywords for writing asynchronous code that looks synchronous.
Future<T>: Represents a potential value or error of typeTthat will complete later.async: Marks a function as asynchronous; allows the use ofawaitinside it. Anasyncfunction automatically wraps its return value in aFuture.await: Pauses execution until theFutureit’s waiting for completes. Can only be used inside anasyncfunction.
// Function that simulates fetching data (returns a Future)Future<String> fetchData() { // Simulate network delay return Future.delayed(const Duration(seconds: 2), () => 'Data Fetched!');}
// Using async/awaitFuture<void> printData() async { print('Fetching data...'); try { String data = await fetchData(); // Wait for the Future to complete print(data); } catch (e) { print('Error fetching data: $e'); }}
// Using .then() for callbacks (alternative)void printDataWithThen() { print('Fetching data (using .then)...'); fetchData().then((data) { print(data); }).catchError((error) { print('Error fetching data: $error'); });}
void main() async { // main can be async await printData(); print('Finished.'); // printDataWithThen(); // This would run concurrently if not awaited}This covers the Dart essentials needed for Flutter. As you progress, you’ll encounter more advanced features like Streams, Isolates, and Generators.