Welcome to Flutter
Flutter is Google's open-source UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase. It uses the Dart programming language and provides a rich set of pre-designed widgets that follow Material Design (for Android) and Cupertino (for iOS) design languages.
Flutter's key advantage is its "write once, run anywhere" approach while delivering native performance. Unlike other cross-platform frameworks that use web views or JavaScript bridges, Flutter compiles to native ARM code and includes its own rendering engine, resulting in smooth animations and fast performance.
Popular apps built with Flutter include Google Ads, Alibaba, Reflectly, and many more, demonstrating its capability to handle complex, production-level applications.
Introduction to Flutter
How to set up a Flutter development environment and create your first "Hello, World!" application.
Step 1: Install Flutter SDK
-
Windows
Download the Flutter SDK from flutter.dev and extract it to your desired location. Add the flutter\bin directory to your PATH environment variable. -
macOS
Download the Flutter SDK and extract it. Alternatively, use Homebrew:brew install --cask flutter -
Linux
Download and extract Flutter, then add to PATH:export PATH="$PATH:`pwd`/flutter/bin"
Step 2: Install an IDE
Recommended IDEs for Flutter development:
- Android Studio with Flutter and Dart plugins
- Visual Studio Code with Flutter extension
- IntelliJ IDEA with Flutter and Dart plugins
Step 3: Verify Installation
Run the following command to check your Flutter installation:
flutter doctor
This command checks your environment and displays a report of the status of your Flutter installation.
Step 4: Create and Run Your First Flutter App
-
Create a new Flutter project:
flutter create my_first_app -
Navigate to the project directory:
cd my_first_app -
Run the app:
flutter run
Step 5: Understanding the Basic Structure
Open lib/main.dart to see your first Flutter app:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello, World!'),
),
),
);
}
}
Congratulations! You have successfully set up Flutter and run your first app. 🎉
Dart Syntax Basics
Dart is the programming language used by Flutter. It's a modern, object-oriented language with C-style syntax that supports both just-in-time (JIT) and ahead-of-time (AOT) compilation.
1. Basic Structure of a Dart Program
Every Dart program starts execution from the main() function:
// This is a simple Dart program
void main() { // Main function - program entry point
print('Hello, Dart!'); // Print to console
}
2. Semicolons and Code Blocks
Dart uses semicolons to terminate statements and braces {} to define code blocks:
void main() {
int x = 5; // Statement ends with semicolon
if (x > 3) { // Braces define the if block
print('x is greater than 3');
} // Closing brace
}
3. Comments
Dart supports single-line, multi-line, and documentation comments:
// This is a single-line comment
/*
This is a multi-line comment
It can span multiple lines
*/
/// This is a documentation comment
/// Used for generating documentation
void calculate() {
// Implementation here
}
4. Case Sensitivity
Dart is case-sensitive, meaning it distinguishes between uppercase and lowercase letters:
void main() {
String myVar = 'hello'; // Different from myvar
String MyVar = 'WORLD'; // Different from myVar
print(myVar); // Outputs: hello
print(MyVar); // Outputs: WORLD
}
5. Type Inference with var and final
Dart supports type inference, allowing you to omit explicit type declarations:
void main() {
var name = 'Dart'; // Type inferred as String
final version = 2.12; // Runtime constant
const pi = 3.14159; // Compile-time constant
print(name.runtimeType); // Outputs: String
print(version.runtimeType); // Outputs: double
}
Conclusion
Understanding Dart syntax is essential for Flutter development. Key takeaways include:
- Dart programs start execution from the
main()function - Statements end with semicolons
- Code blocks are defined with braces
{} - Dart is case-sensitive
- Dart supports type inference with
var,final, andconst
Widgets in Flutter
In Flutter, everything is a widget. Widgets are the building blocks of a Flutter app's user interface. They describe what their view should look like given their current configuration and state.
1. Basic Widget Structure
Widgets are typically built by composing smaller widgets:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Center(
child: Text('Hello, Flutter!'),
),
),
),
);
}
2. Common Built-in Widgets
Flutter provides a rich set of built-in widgets:
Column(
children: [
Text('Hello World', style: TextStyle(fontSize: 24)),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
print('Button pressed!');
},
child: Text('Click Me'),
),
Image.network('https://example.com/image.jpg'),
],
)
3. Widget Composition
You can create complex UIs by composing simple widgets:
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.album),
title: Text('The Enchanted Nightingale'),
subtitle: Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
child: Text('BUY TICKETS'),
onPressed: () {},
),
SizedBox(width: 8),
TextButton(
child: Text('LISTEN'),
onPressed: () {},
),
],
),
],
),
),
)
4. Custom Widgets
You can create your own reusable widgets:
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
CustomButton({required this.text, required this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
}
Conclusion
Widgets are fundamental to Flutter development. Key points:
- Everything in Flutter is a widget
- Widgets are immutable and describe parts of the UI
- Complex UIs are built by composing simple widgets
- You can create custom reusable widgets
Arithmetic Operators in Dart
Dart provides standard arithmetic operators for mathematical calculations. These operators work with numeric data types like int and double.
Basic Arithmetic Operators
void main() {
int a = 15, b = 4;
print('a + b = ${a + b}'); // Addition: 19
print('a - b = ${a - b}'); // Subtraction: 11
print('a * b = ${a * b}'); // Multiplication: 60
print('a / b = ${a / b}'); // Division: 3.75 (always returns double)
print('a ~/ b = ${a ~/ b}'); // Integer division: 3
print('a % b = ${a % b}'); // Modulus: 3
double x = 15.0, y = 4.0;
print('x / y = ${x / y}'); // Division: 3.75
}
Increment and Decrement Operators
void main() {
int count = 5;
print('count = $count'); // 5
print('++count = ${++count}'); // 6 (pre-increment)
print('count++ = ${count++}'); // 6 (post-increment)
print('count = $count'); // 7
print('--count = ${--count}'); // 6 (pre-decrement)
}
Operator Precedence
void main() {
int result = 2 + 3 * 4; // Multiplication before addition
print(result); // 14
result = (2 + 3) * 4; // Parentheses change precedence
print(result); // 20
double complex = 10 + 5 / 2 - 3 * 2;
print(complex); // 10 + 2.5 - 6 = 6.5
}
Common Pitfalls
- The
/operator always returns adouble, even for integer operands - Use
~/for integer division that truncates the result - Division by zero returns
double.infinityordouble.negativeInfinity, not an error
Comparison Operators in Dart
Comparison operators compare two values and return a boolean result (true or false). These are essential for conditional statements and control flow.
Comparison Operators
void main() {
int x = 7, y = 10;
print('x == y: ${x == y}'); // Equal to: false
print('x != y: ${x != y}'); // Not equal to: true
print('x > y: ${x > y}'); // Greater than: false
print('x < y: ${x < y}'); // Less than: true
print('x >= 7: ${x >= 7}'); // Greater than or equal: true
print('y <= 7: ${y <= 7}'); // Less than or equal: false
// String comparison
String a = 'apple', b = 'banana';
print('a == b: ${a == b}'); // false
print('a < b: ${a < b}'); // true (lexicographical order)
}
Using Comparisons in Conditions
void main() {
int age = 18;
bool hasLicense = true;
if (age >= 18 && hasLicense) {
print('You can drive');
} else {
print('You cannot drive');
}
// Comparing objects
var point1 = Point(1, 2);
var point2 = Point(1, 2);
print('point1 == point2: ${point1 == point2}'); // false (different instances)
}
class Point {
final int x, y;
Point(this.x, this.y);
@override
bool operator ==(Object other) {
return other is Point && other.x == x && other.y == y;
}
@override
int get hashCode => Object.hash(x, y);
}
Common Pitfalls
- Objects are compared by reference by default, not by value
- Override
==operator andhashCodefor value equality - Be careful when comparing floating-point numbers due to precision issues
Logical Operators in Dart
Logical operators combine boolean expressions and return true or false. Dart provides three logical operators: && (AND), || (OR), and ! (NOT).
Logical Operators
void main() {
bool isSunny = true;
bool isWarm = false;
print('isSunny && isWarm: ${isSunny && isWarm}'); // AND: false
print('isSunny || isWarm: ${isSunny || isWarm}'); // OR: true
print('!isWarm: ${!isWarm}'); // NOT: true
// Complex conditions
int age = 25;
bool hasLicense = true;
bool hasCar = false;
if ((age >= 18 && hasLicense) || hasCar) {
print('You can drive to the event');
} else {
print('You need transportation');
}
}
Short-Circuit Evaluation
void main() {
// In AND (&&), if first condition is false, second isn't evaluated
// In OR (||), if first condition is true, second isn't evaluated
String? name;
// Safe null check with short-circuit
if (name != null && name.isNotEmpty) {
print('Hello, $name');
} else {
print('Name is null or empty');
}
// This would cause an error without short-circuit evaluation
// if (name.isNotEmpty && name != null) { ... }
}
Truth Tables
void demonstrateTruthTables() {
print('AND (&&) Truth Table:');
print('true && true = ${true && true}'); // true
print('true && false = ${true && false}'); // false
print('false && true = ${false && true}'); // false
print('false && false = ${false && false}');// false
print('\nOR (||) Truth Table:');
print('true || true = ${true || true}'); // true
print('true || false = ${true || false}'); // true
print('false || true = ${false || true}'); // true
print('false || false = ${false || false}'); // false
print('\nNOT (!) Truth Table:');
print('!true = ${!true}'); // false
print('!false = ${!false}'); // true
}
Common Pitfalls
- Using bitwise operators (
&,|) instead of logical operators (&&,||) - Forgetting that logical operators have lower precedence than comparison operators
- Not leveraging short-circuit evaluation for safe null checks
Bitwise Operators in Dart
Bitwise operators perform operations on individual bits of integer types. While less common in Flutter than in system programming, they're useful for flags, permissions, and low-level data manipulation.
Bitwise Operators
void main() {
int a = 6; // binary: 0110
int b = 3; // binary: 0011
print('a = ${a.toRadixString(2).padLeft(4, '0')} ($a)');
print('b = ${b.toRadixString(2).padLeft(4, '0')} ($b)');
print('a & b = ${(a & b).toRadixString(2).padLeft(4, '0')} (${a & b})'); // AND: 0010 (2)
print('a | b = ${(a | b).toRadixString(2).padLeft(4, '0')} (${a | b})'); // OR: 0111 (7)
print('a ^ b = ${(a ^ b).toRadixString(2).padLeft(4, '0')} (${a ^ b})'); // XOR: 0101 (5)
print('~a = ${(~a).toRadixString(2)} (${~a})'); // NOT: ...11111111111111111111111111111001 (-7)
print('a << 1 = ${(a << 1).toRadixString(2).padLeft(4, '0')} (${a << 1})'); // Left shift: 1100 (12)
print('b >> 1 = ${(b >> 1).toRadixString(2).padLeft(4, '0')} (${b >> 1})'); // Right shift: 0001 (1)
}
Practical Applications
class Permissions {
static const int READ = 1; // 0001
static const int WRITE = 2; // 0010
static const int EXECUTE = 4; // 0100
static const int DELETE = 8; // 1000
int userPermissions = 0;
void grantPermission(int permission) {
userPermissions |= permission;
}
void revokePermission(int permission) {
userPermissions &= ~permission;
}
bool hasPermission(int permission) {
return (userPermissions & permission) == permission;
}
void printPermissions() {
print('Read: ${hasPermission(READ)}');
print('Write: ${hasPermission(WRITE)}');
print('Execute: ${hasPermission(EXECUTE)}');
print('Delete: ${hasPermission(DELETE)}');
}
}
void main() {
var perms = Permissions();
perms.grantPermission(Permissions.READ | Permissions.WRITE);
perms.printPermissions();
perms.revokePermission(Permissions.WRITE);
perms.printPermissions();
}
Bit Manipulation Utilities
void main() {
int number = 42;
// Check if a specific bit is set
bool isBitSet(int n, int bitPosition) {
return (n & (1 << bitPosition)) != 0;
}
// Set a specific bit
int setBit(int n, int bitPosition) {
return n | (1 << bitPosition);
}
// Clear a specific bit
int clearBit(int n, int bitPosition) {
return n & ~(1 << bitPosition);
}
print('Number: ${number.toRadixString(2)}');
print('Bit 3 is set: ${isBitSet(number, 3)}');
number = setBit(number, 3);
print('After setting bit 3: ${number.toRadixString(2)}');
number = clearBit(number, 1);
print('After clearing bit 1: ${number.toRadixString(2)}');
}
Common Pitfalls
- Confusing bitwise AND
&with logical AND&& - Dart integers are signed, so right-shifting preserves the sign bit
- Bitwise operations on negative numbers can have unexpected results
- Use
>>>for unsigned right shift that doesn't preserve sign
Assignment Operators in Dart
Assignment operators assign values to variables. Dart provides compound assignment operators that combine assignment with arithmetic or bitwise operations.
Assignment Operators
void main() {
int x = 10; // Simple assignment
print('x = $x');
x += 5; // Equivalent to x = x + 5
print('x += 5: $x'); // 15
x -= 3; // Equivalent to x = x - 3
print('x -= 3: $x'); // 12
x *= 2; // Equivalent to x = x * 2
print('x *= 2: $x'); // 24
x ~/= 4; // Equivalent to x = x ~/ 4 (integer division)
print('x ~/= 4: $x'); // 6
x %= 4; // Equivalent to x = x % 4
print('x %= 4: $x'); // 2
// Bitwise assignment operators
int y = 5;
y &= 3; // AND assignment: y = y & 3
y |= 8; // OR assignment: y = y | 8
y ^= 4; // XOR assignment: y = y ^ 4
// Null-aware assignment (discussed in null-aware operators section)
String? name;
name ??= 'Unknown'; // Assign if null
print('Name: $name');
}
Multiple Assignment and Cascade Operator
void main() {
// Multiple assignment
int a, b, c;
a = b = c = 10; // All variables get value 10
// Compound assignment in loops
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i; // Add i to sum
}
print('Sum: $sum'); // 15
// Cascade operator (..) for method chaining
var stringBuffer = StringBuffer()
..write('Hello')
..write(' ')
..write('World');
print(stringBuffer.toString()); // Hello World
}
Assignment with Different Data Types
void main() {
// List assignment
var list1 = [1, 2, 3];
var list2 = list1; // Both reference same list
list2[0] = 99;
print('list1: $list1'); // [99, 2, 3] - both changed!
// Copying lists to avoid shared references
var list3 = [...list1]; // Spread operator creates copy
list3[0] = 100;
print('list1: $list1'); // [99, 2, 3] - unchanged
print('list3: $list3'); // [100, 2, 3]
// Map assignment
var map1 = {'a': 1, 'b': 2};
var map2 = {...map1}; // Copy map
map2['a'] = 99;
print('map1: $map1'); // {a: 1, b: 2} - unchanged
print('map2: $map2'); // {a: 99, b: 2}
}
Common Pitfalls
- Using
=(assignment) when you meant==(equality comparison) - Forgetting that list and map assignments create references, not copies
- Using wrong compound operator (e.g.,
+=instead of~/=) - Not understanding the cascade operator's purpose and usage
Null-Aware Operators in Dart
Dart provides special operators for handling null values safely. These operators are essential for writing null-safe code and avoiding null reference exceptions.
Null-Aware Operators
void main() {
String? nullableString; // Can be null
String nonNullableString = 'Hello'; // Cannot be null
// Null-aware assignment (??=)
nullableString ??= 'Default Value';
print('nullableString: $nullableString'); // Default Value
// Null-aware access (?.)
print('Length: ${nullableString?.length}'); // 13
print('Uppercase: ${nullableString?.toUpperCase()}'); // DEFAULT VALUE
// Null-coalescing operator (??)
String result = nullableString ?? 'Fallback';
print('Result: $result'); // Default Value
String? completelyNull;
String fallbackResult = completelyNull ?? 'Fallback Value';
print('Fallback: $fallbackResult'); // Fallback Value
// Null-aware cascade (?..)
var stringBuffer = StringBuffer()
?..write('Hello')
?..write(' ')
?..write('World');
print('Buffer: ${stringBuffer.toString()}');
}
Null Safety in Practice
class User {
final String name;
final int? age; // age can be null
User({required this.name, this.age});
void printInfo() {
print('Name: $name');
// Safe null handling
if (age != null) {
print('Age: $age');
}
// Using null-aware operator
print('Age: ${age ?? 'Not specified'}');
// Method call with null safety
print('Age next year: ${age?.add(1) ?? 'Unknown'}');
}
}
void processUser(User? user) {
// Early return if null
if (user == null) return;
// Using null assertion when sure it's not null
print('User name: ${user.name}');
// Safe method calls
user.printInfo();
}
void main() {
var user1 = User(name: 'Alice', age: 25);
var user2 = User(name: 'Bob'); // age is null
processUser(user1);
processUser(user2);
processUser(null); // Safely handled
}
Advanced Null-Aware Patterns
void main() {
// Chaining null-aware operators
Map<String, Map<String, String>>? complexData = {
'user': {'name': 'Alice', 'email': 'alice@example.com'}
};
String? email = complexData?['user']?['email'];
print('Email: $email'); // alice@example.com
// With completely null data
Map<String, Map<String, String>>? nullData = null;
String? nullEmail = nullData?['user']?['email'];
print('Null Email: $nullEmail'); // null
// Using with collections
List<String?> names = ['Alice', null, 'Bob', null, 'Charlie'];
// Filter out nulls
var nonNullNames = names.where((name) => name != null).toList();
print('Non-null names: $nonNullNames'); // [Alice, Bob, Charlie]
// Using whereType to filter nulls
var typedNames = names.whereType<String>().toList();
print('Typed names: $typedNames'); // [Alice, Bob, Charlie]
// Null-aware in function parameters
void printMessage([String? message]) {
print(message ?? 'No message provided');
}
printMessage('Hello!'); // Hello!
printMessage(); // No message provided
}
Common Pitfalls
- Using
!(null assertion) without being sure the value isn't null - Forgetting to handle null cases in conditional logic
- Not using null-aware operators when working with nullable types
- Overusing null assertion operator instead of proper null handling
Numbers in Dart
Dart provides two types of numbers: int for integers and double for floating-point numbers. Both are subclasses of num.
Integer and Double Types
void main() {
// Integer types
int age = 25;
int hexValue = 0xDEADBEEF;
int population = 8000000000;
// Double types
double price = 19.99;
double exponent = 1.42e5;
double percentage = 0.75;
// Num type (can be both int or double)
num flexibleNumber = 10;
flexibleNumber = 10.5;
print('Age: $age');
print('Price: $price');
print('Exponent: $exponent');
print('Hex: $hexValue');
// Number properties
print('age.isEven: ${age.isEven}');
print('age.isOdd: ${age.isOdd}');
print('price.isNaN: ${price.isNaN}');
print('price.isInfinite: ${price.isInfinite}');
}
Number Operations and Methods
void main() {
int a = 10, b = 3;
double x = 10.0, y = 3.0;
// Basic operations
print('a + b = ${a + b}'); // 13
print('a - b = ${a - b}'); // 7
print('a * b = ${a * b}'); // 30
print('a / b = ${a / b}'); // 3.333... (returns double)
print('a ~/ b = ${a ~/ b}'); // 3 (integer division)
print('a % b = ${a % b}'); // 1
// Type conversion
String numberString = '123';
int fromString = int.parse(numberString);
double fromStringDouble = double.parse('123.45');
String toString = 123.toString();
String toStringFixed = 123.456.toStringAsFixed(2); // "123.46"
// Mathematical operations
print('Absolute: ${(-10).abs()}'); // 10
print('Round: ${10.6.round()}'); // 11
print('Floor: ${10.9.floor()}'); // 10
print('Ceil: ${10.1.ceil()}'); // 11
print('Power: ${pow(2, 3)}'); // 8.0
print('Square root: ${sqrt(16)}'); // 4.0
}
Working with Different Number Bases
void main() {
// Different number bases
int decimal = 100;
int hex = 0x64; // hexadecimal
int octal = 0144; // octal (leading 0)
int binary = 0b1100100; // binary
print('Decimal: $decimal');
print('Hex: $hex');
print('Octal: $octal');
print('Binary: $binary');
// Converting between bases
print('100 in binary: ${decimal.toRadixString(2)}'); // 1100100
print('100 in hex: ${decimal.toRadixString(16)}'); // 64
print('100 in octal: ${decimal.toRadixString(8)}'); // 144
// Parsing from different bases
int fromHex = int.parse('64', radix: 16);
int fromBinary = int.parse('1100100', radix: 2);
print('From hex: $fromHex'); // 100
print('From binary: $fromBinary'); // 100
}
Common Pitfalls
- The
/operator always returnsdouble, even with integer operands - Integer division uses
~/operator, not/ - Parsing invalid strings throws
FormatException - Large integers may lose precision when converted to double
Booleans in Dart
The bool type represents boolean values true and false. Dart is strict about boolean values and doesn't allow truthy/falsy values like other languages.
Boolean Basics
void main() {
bool isActive = true;
bool isCompleted = false;
bool hasPermission = true;
print('isActive: $isActive');
print('isCompleted: $isCompleted');
print('hasPermission: $hasPermission');
// Boolean operations
print('AND: ${isActive && hasPermission}'); // true
print('OR: ${isActive || isCompleted}'); // true
print('NOT: ${!isCompleted}'); // true
// Boolean from expressions
int age = 25;
bool isAdult = age >= 18;
bool canVote = isAdult && hasPermission;
print('Is adult: $isAdult'); // true
print('Can vote: $canVote'); // true
}
Strict Boolean Checking
void main() {
// Dart requires explicit boolean values in conditions
String name = 'Alice';
List<String> items = ['apple', 'banana'];
int count = 0;
// These work fine
if (name.isNotEmpty) {
print('Name is not empty');
}
if (items.length > 0) {
print('Items list is not empty');
}
if (count == 0) {
print('Count is zero');
}
// These would cause compilation errors:
// if (name) { ... } // Error: String can't be used as bool
// if (items) { ... } // Error: List can't be used as bool
// if (count) { ... } // Error: int can't be used as bool
// Proper ways to check:
if (name != null && name.isNotEmpty) {
print('Valid name');
}
if (items.isNotEmpty) {
print('Items available');
}
if (count > 0) {
print('Positive count');
}
}
Boolean in Conditional Expressions
void main() {
bool isLoggedIn = true;
String userRole = 'admin';
int loginAttempts = 3;
// Conditional assignment
String status = isLoggedIn ? 'Logged In' : 'Logged Out';
String accessLevel = userRole == 'admin' ? 'Full Access' : 'Limited Access';
print('Status: $status');
print('Access: $accessLevel');
// Complex conditions
bool canAccessAdminPanel = isLoggedIn && userRole == 'admin';
bool shouldShowWarning = loginAttempts > 5 || !isLoggedIn;
bool isSuspended = loginAttempts >= 10 && !isLoggedIn;
print('Admin access: $canAccessAdminPanel');
print('Show warning: $shouldShowWarning');
print('Suspended: $isSuspended');
// Using boolean in switch (not directly possible)
// But you can use if-else chains
if (canAccessAdminPanel) {
print('Redirecting to admin panel...');
} else if (shouldShowWarning) {
print('Please check your credentials');
} else {
print('Access denied');
}
}
Boolean Methods and Properties
class User {
final String name;
final bool isVerified;
final bool isActive;
User(this.name, this.isVerified, this.isActive);
bool get canPost => isVerified && isActive;
bool get needsVerification => !isVerified && isActive;
void printStatus() {
print('User: $name');
print('Verified: $isVerified');
print('Active: $isActive');
print('Can post: $canPost');
print('Needs verification: $needsVerification');
// Using boolean in string interpolation
print('${name} is ${isActive ? "active" : "inactive"}');
}
}
void main() {
var user1 = User('Alice', true, true);
var user2 = User('Bob', false, true);
user1.printStatus();
user2.printStatus();
}
Common Pitfalls
- Dart doesn't support truthy/falsy values - must use explicit boolean expressions
- Forgetting to handle null cases in boolean expressions
- Using assignment (
=) instead of comparison (==) in conditions - Not using parentheses for complex boolean expressions, leading to precedence issues
Strings in Dart
Strings in Dart are sequences of UTF-16 code units. Dart provides powerful string manipulation capabilities including interpolation, multiline strings, and raw strings.
Creating and Using Strings
void main() {
// Different ways to create strings
String s1 = 'Hello';
String s2 = "World";
String s3 = '''This is a
multiline string''';
String s4 = """This is also
a multiline string""";
// Raw strings (no escape processing)
String raw = r'Raw string: \n no new line here';
// String concatenation
String greeting = s1 + ' ' + s2;
String greeting2 = '$s1 $s2'; // String interpolation
print('s1: $s1');
print('s2: $s2');
print('s3: $s3');
print('s4: $s4');
print('raw: $raw');
print('greeting: $greeting');
print('greeting2: $greeting2');
// String properties
print('Length: ${s1.length}');
print('Is empty: ${s1.isEmpty}');
print('Is not empty: ${s1.isNotEmpty}');
}
String Access and Manipulation
void main() {
String text = 'Hello Dart Programming';
// Accessing characters
print('First character: ${text[0]}'); // H
print('Last character: ${text[text.length - 1]}'); // g
// Substrings
print('Substring 0-5: ${text.substring(0, 5)}'); // Hello
print('Substring from 6: ${text.substring(6)}'); // Dart Programming
// Checking content
print('Starts with Hello: ${text.startsWith('Hello')}'); // true
print('Ends with ing: ${text.endsWith('ing')}'); // true
print('Contains Dart: ${text.contains('Dart')}'); // true
// Case conversion
print('Uppercase: ${text.toUpperCase()}');
print('Lowercase: ${text.toLowerCase()}');
// Trimming
String padded = ' Hello ';
print('Trimmed: "${padded.trim()}"');
print('Left trimmed: "${padded.trimLeft()}"');
print('Right trimmed: "${padded.trimRight()}"');
}
String Searching and Replacement
void main() {
String message = 'Hello world, welcome to the world of Dart';
// Finding positions
print('Index of world: ${message.indexOf('world')}'); // 6
print('Last index of world: ${message.lastIndexOf('world')}'); // 23
// Checking if string matches pattern
print('Matches pattern: ${message.contains(RegExp(r'[0-9]'))}'); // false
// Replacement
print('Replace world: ${message.replaceAll('world', 'Dart')}');
print('Replace first: ${message.replaceFirst('world', 'Dart')}');
// Regular expressions
var regExp = RegExp(r'\b\w{5}\b'); // 5-letter words
var matches = regExp.allMatches(message);
print('5-letter words:');
for (var match in matches) {
print(' - ${message.substring(match.start, match.end)}');
}
// Splitting strings
List<String> words = message.split(' ');
print('Words: $words');
print('First 3 words: ${words.take(3).toList()}');
}
Common Pitfalls
- Accessing characters beyond string length causes
RangeError - String interpolation with complex expressions needs curly braces:
${expression} - Forgetting that strings are immutable - operations return new strings
- Not handling Unicode characters properly (use
runesfor Unicode code points)
String Functions in Dart
Dart's String class provides numerous methods for string manipulation, validation, transformation, and pattern matching.
String Validation and Checking
void main() {
String email = 'user@example.com';
String phone = '+1-555-123-4567';
String username = 'user_123';
// Validation methods
print('Email is empty: ${email.isEmpty}');
print('Email is not empty: ${email.isNotEmpty}');
// Pattern checking
print('Contains @: ${email.contains('@')}');
print('StartsWith user: ${email.startsWith('user')}');
print('EndsWith .com: ${email.endsWith('.com')}');
// Regular expression validation
bool isValidEmail(String email) {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}
bool isValidPhone(String phone) {
return RegExp(r'^[\d\-\+\s\(\)]+$').hasMatch(phone);
}
bool isValidUsername(String username) {
return RegExp(r'^[a-zA-Z0-9_]{3,20}$').hasMatch(username);
}
print('Valid email: ${isValidEmail(email)}');
print('Valid phone: ${isValidPhone(phone)}');
print('Valid username: ${isValidUsername(username)}');
}
String Transformation Methods
void main() {
String text = ' Hello Dart Programming ';
// Padding
print('Padded left: ${text.padLeft(30, '-')}');
print('Padded right: ${text.padRight(30, '-')}');
// Case conversion with locale
print('Turkish lowercase: ${'İ'.toLowerCase()}'); // i
print('Turkish uppercase: ${'i'.toUpperCase()}'); // İ
// String iteration
print('Characters:');
for (int i = 0; i < text.length; i++) {
print(' ${text[i]} (${text.codeUnitAt(i)})');
}
// Using runes for Unicode characters
String emoji = '😀🎉🚀';
print('Emoji string: $emoji');
print('Emoji runes: ${emoji.runes.toList()}');
print('Emoji length: ${emoji.length}'); // 6 (code units)
print('Emoji runes length: ${emoji.runes.length}'); // 3 (code points)
// Comparing strings
String str1 = 'apple';
String str2 = 'APPLE';
String str3 = 'apple';
print('str1 == str2: ${str1 == str2}'); // false
print('str1 == str3: ${str1 == str3}'); // true
print('Case insensitive: ${str1.toUpperCase() == str2.toUpperCase()}'); // true
}
Advanced String Operations
void main() {
// String buffers for efficient concatenation
StringBuffer buffer = StringBuffer();
buffer
..write('Hello')
..write(' ')
..write('Dart')
..write('!');
String result = buffer.toString();
print('Buffer result: $result');
// Building complex strings
String buildTable(List<List<String>> data) {
var buffer = StringBuffer();
buffer.writeln('┌────────────┬────────────┐');
for (var row in data) {
buffer.write('│ ${row[0].padRight(10)} │ ${row[1].padRight(10)} │\n');
}
buffer.writeln('└────────────┴────────────┘');
return buffer.toString();
}
var tableData = [
['Name', 'Age'],
['Alice', '25'],
['Bob', '30'],
['Charlie', '35']
];
print(buildTable(tableData));
// String manipulation with extensions
extension StringExtensions on String {
String get reversed => split('').reversed.join();
String get capitalized => this[0].toUpperCase() + substring(1).toLowerCase();
String get slugify => toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '-');
}
print('Reversed: ${'hello'.reversed}');
print('Capitalized: ${'dart'.capitalized}');
print('Slugified: ${'Hello World Dart!'.slugify}');
}
Common Pitfalls
- Using
+for concatenation in loops (inefficient - useStringBuffer) - Confusing string length with rune count for Unicode characters
- Not considering locale in case conversions
- Forgetting that string methods return new strings rather than modifying in place
String Interpolation in Dart
String interpolation allows you to embed expressions and variables within string literals. Dart provides powerful interpolation features that make string building clean and readable.
Basic String Interpolation
void main() {
String name = 'Alice';
int age = 25;
double score = 95.5;
// Basic variable interpolation
print('Name: $name'); // Name: Alice
print('Age: $age'); // Age: 25
print('Score: $score'); // Score: 95.5
// Expression interpolation (requires curly braces)
print('Next year: ${age + 1}'); // Next year: 26
print('Percentage: ${score}%'); // Percentage: 95.5%
print('Initial: ${name[0]}'); // Initial: A
print('Uppercase: ${name.toUpperCase()}'); // Uppercase: ALICE
// Complex expressions
bool isAdult = age >= 18;
print('Status: ${isAdult ? 'Adult' : 'Minor'}'); // Status: Adult
print('Length: ${name.length} characters'); // Length: 5 characters
}
Multi-line String Interpolation
class Product {
final String name;
final double price;
final int quantity;
Product(this.name, this.price, this.quantity);
double get total => price * quantity;
String get receipt {
return '''
PRODUCT: $name
PRICE: \$$price
QUANTITY: $quantity
TOTAL: \$$total
''';
}
}
void main() {
var product = Product('Laptop', 999.99, 2);
print(product.receipt);
// Building complex strings with interpolation
List<Map<String, dynamic>> users = [
{'name': 'Alice', 'age': 25, 'active': true},
{'name': 'Bob', 'age': 30, 'active': false},
{'name': 'Charlie', 'age': 35, 'active': true},
];
String userList = '''
ACTIVE USERS:
${users.where((user) => user['active'] == true).map((user) => ' - ${user['name']} (${user['age']})').join('\n')}
INACTIVE USERS:
${users.where((user) => user['active'] == false).map((user) => ' - ${user['name']} (${user['age']})').join('\n')}
''';
print(userList);
}
Advanced Interpolation Patterns
void main() {
// Formatting numbers in interpolation
double pi = 3.14159265359;
print('Pi: ${pi.toStringAsFixed(2)}'); // Pi: 3.14
print('Pi: ${pi.toStringAsPrecision(3)}'); // Pi: 3.14
// Date interpolation
DateTime now = DateTime.now();
print('Current time: ${now.hour}:${now.minute.toString().padLeft(2, '0')}');
print('Today: ${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}');
// Collection interpolation
List<int> numbers = [1, 2, 3, 4, 5];
print('Numbers: $numbers');
print('First three: ${numbers.take(3).toList()}');
print('Sum: ${numbers.reduce((a, b) => a + b)}');
// Conditional interpolation
String? optionalName;
String displayName = 'Hello, ${optionalName ?? 'Guest'}!';
print(displayName); // Hello, Guest!
// Method chain interpolation
String sentence = ' hello world ';
print('Processed: ${sentence.trim().toUpperCase().replaceAll('WORLD', 'DART')}');
// Building URLs with interpolation
String baseUrl = 'https://api.example.com';
String endpoint = 'users';
int userId = 123;
String url = '$baseUrl/$endpoint/$userId';
print('API URL: $url');
}
Custom Interpolation with toString()
class Person {
final String firstName;
final String lastName;
final DateTime birthDate;
Person(this.firstName, this.lastName, this.birthDate);
String get fullName => '$firstName $lastName';
int get age {
final now = DateTime.now();
return now.year - birthDate.year - (now.month > birthDate.month ||
(now.month == birthDate.month && now.day >= birthDate.day) ? 0 : 1);
}
@override
String toString() {
return 'Person(name: $fullName, age: $age)';
}
String toJsonString() {
return '''
{
"firstName": "$firstName",
"lastName": "$lastName",
"birthDate": "${birthDate.toIso8601String()}",
"age": $age
}
''';
}
}
void main() {
var person = Person('John', 'Doe', DateTime(1990, 5, 15));
// Automatic toString() interpolation
print('Person: $person');
// Custom formatted string
print('JSON: ${person.toJsonString()}');
// Complex interpolation with custom objects
print('''
PERSON DETAILS:
Name: ${person.fullName}
Age: ${person.age}
Birth Year: ${person.birthDate.year}
Adult: ${person.age >= 18 ? 'Yes' : 'No'}
''');
}
Common Pitfalls
- Forgetting curly braces
{}for expressions:${expression} - Using string interpolation for sensitive data (can expose information in logs)
- Not handling null values in interpolation expressions
- Complex expressions in interpolation reducing readability
Lists in Dart
Lists are ordered collections of objects. Dart lists are similar to arrays in other languages and can grow or shrink dynamically.
Creating and Using Lists
void main() {
// List creation
List<int> numbers = [1, 2, 3, 4, 5];
List<String> names = ['Alice', 'Bob', 'Charlie'];
var mixed = [1, 'hello', 3.14, true]; // List<Object>
// Empty lists
List<int> empty1 = [];
List<int> empty2 = List.empty();
var empty3 = <int>[];
// Fixed-length lists
List<int> fixed = List.filled(3, 0); // [0, 0, 0]
List<String> generated = List.generate(5, (i) => 'Item ${i + 1}');
print('Numbers: $numbers');
print('Names: $names');
print('Mixed: $mixed');
print('Fixed: $fixed');
print('Generated: $generated');
// List properties
print('Length: ${numbers.length}');
print('Is empty: ${numbers.isEmpty}');
print('Is not empty: ${numbers.isNotEmpty}');
print('First: ${numbers.first}');
print('Last: ${numbers.last}');
}
List Operations and Access
void main() {
List<int> numbers = [10, 20, 30, 40, 50];
// Accessing elements
print('First: ${numbers[0]}'); // 10
print('Last: ${numbers[numbers.length - 1]}'); // 50
print('Element at 2: ${numbers.elementAt(2)}'); // 30
// Modifying lists
numbers[1] = 25; // Update element
numbers.add(60); // Add to end
numbers.insert(2, 35); // Insert at position
numbers.addAll([70, 80, 90]); // Add multiple
print('After modifications: $numbers');
// Removing elements
numbers.remove(35); // Remove by value
numbers.removeAt(0); // Remove by index
numbers.removeLast(); // Remove last
numbers.removeRange(1, 3); // Remove range
print('After removals: $numbers');
// Sublists
List<int> sublist = numbers.sublist(1, 3);
print('Sublist: $sublist');
// Checking conditions
print('Contains 50: ${numbers.contains(50)}');
print('Every > 0: ${numbers.every((n) => n > 0)}');
print('Any > 100: ${numbers.any((n) => n > 100)}');
}
List Iteration and Transformation
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// Different ways to iterate
print('For loop:');
for (int i = 0; i < numbers.length; i++) {
print(' numbers[$i] = ${numbers[i]}');
}
print('For-in loop:');
for (int number in numbers) {
print(' $number');
}
print('ForEach:');
numbers.forEach((number) => print(' $number'));
// Transformation methods
List<int> doubled = numbers.map((n) => n * 2).toList();
List<int> even = numbers.where((n) => n % 2 == 0).toList();
List<String> strings = numbers.map((n) => 'Number $n').toList();
print('Doubled: $doubled');
print('Even: $even');
print('Strings: $strings');
// Reduction methods
int sum = numbers.reduce((a, b) => a + b);
int product = numbers.fold(1, (a, b) => a * b);
int max = numbers.reduce((a, b) => a > b ? a : b);
print('Sum: $sum');
print('Product: $product');
print('Max: $max');
}
Common Pitfalls
- Accessing indices beyond list length causes
RangeError - Modifying lists while iterating can cause
ConcurrentModificationError - Forgetting that
map()returnsIterable, notList - Not using const lists for immutable collections when appropriate
List Functions and Methods
Dart provides extensive functionality for list manipulation including sorting, searching, and functional programming operations.
List Sorting and Ordering
void main() {
List<int> numbers = [5, 2, 8, 1, 9, 3];
List<String> names = ['Charlie', 'Alice', 'Bob'];
// Basic sorting
numbers.sort();
names.sort();
print('Sorted numbers: $numbers'); // [1, 2, 3, 5, 8, 9]
print('Sorted names: $names'); // [Alice, Bob, Charlie]
// Custom sorting
List<String> words = ['apple', 'banana', 'cherry', 'date'];
words.sort((a, b) => a.length.compareTo(b.length));
print('Sorted by length: $words'); // [date, apple, banana, cherry]
// Reverse order
List<int> reversed = numbers.reversed.toList();
print('Reversed: $reversed'); // [9, 8, 5, 3, 2, 1]
// Shuffling
List<int> shuffled = [...numbers]..shuffle();
print('Shuffled: $shuffled');
}
List Searching and Filtering
void main() {
List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Searching
print('Index of 5: ${numbers.indexOf(5)}'); // 4
print('Last index of 5: ${numbers.lastIndexOf(5)}'); // 4
print('First > 5: ${numbers.firstWhere((n) => n > 5)}'); // 6
print('Last < 5: ${numbers.lastWhere((n) => n < 5)}'); // 4
// Filtering
List<int> even = numbers.where((n) => n % 2 == 0).toList();
List<int> odd = numbers.where((n) => n % 2 != 0).toList();
List<int> greaterThan5 = numbers.where((n) => n > 5).toList();
print('Even: $even'); // [2, 4, 6, 8, 10]
print('Odd: $odd'); // [1, 3, 5, 7, 9]
print('Greater than 5: $greaterThan5'); // [6, 7, 8, 9, 10]
// Checking conditions
print('All positive: ${numbers.every((n) => n > 0)}'); // true
print('Any negative: ${numbers.any((n) => n < 0)}'); // false
print('Contains 7: ${numbers.contains(7)}'); // true
// Take and skip
print('First 3: ${numbers.take(3).toList()}'); // [1, 2, 3]
print('Skip 5: ${numbers.skip(5).toList()}'); // [6, 7, 8, 9, 10]
print('Take while < 5: ${numbers.takeWhile((n) => n < 5).toList()}'); // [1, 2, 3, 4]
}
Advanced List Operations
void main() {
// List expansion
List<List<int>> listOfLists = [
[1, 2],
[3, 4],
[5, 6]
];
List<int> flattened = listOfLists.expand((list) => list).toList();
print('Flattened: $flattened'); // [1, 2, 3, 4, 5, 6]
// List folding
List<int> numbers = [1, 2, 3, 4, 5];
int sum = numbers.fold(0, (prev, element) => prev + element);
String concatenated = numbers.fold('', (prev, element) => '$prev$element');
print('Sum: $sum'); // 15
print('Concatenated: $concatenated'); // 12345
// List reduction (requires non-empty list)
int max = numbers.reduce((a, b) => a > b ? a : b);
int min = numbers.reduce((a, b) => a < b ? a : b);
print('Max: $max'); // 5
print('Min: $min'); // 1
// List casting and typing
List<Object> objects = [1, 'hello', 3.14, true];
List<int> integers = objects.whereType<int>().toList();
print('Integers: $integers'); // [1]
// List as map
List<String> names = ['Alice', 'Bob', 'Charlie'];
Map<int, String> nameMap = names.asMap();
print('Name map: $nameMap'); // {0: Alice, 1: Bob, 2: Charlie}
}
List Performance and Best Practices
void main() {
// Performance considerations
List<int> numbers = [];
// Inefficient: O(n²) - recreates list each time
for (int i = 0; i < 1000; i++) {
numbers = [...numbers, i];
}
// Efficient: O(n) - uses internal buffer
List<int> efficient = [];
for (int i = 0; i < 1000; i++) {
efficient.add(i);
}
// Even more efficient: pre-allocate capacity
List<int> optimal = List.filled(1000, 0);
for (int i = 0; i < 1000; i++) {
optimal[i] = i;
}
// Or use growable list with initial capacity
List<int> withCapacity = List.empty(growable: true);
withCapacity.length = 1000; // Pre-allocate
// List equality
List<int> list1 = [1, 2, 3];
List<int> list2 = [1, 2, 3];
List<int> list3 = [1, 2, 4];
print('list1 == list2: ${list1 == list2}'); // false (different instances)
print('list1 equals list2: ${listEquals(list1, list2)}'); // true
print('list1 equals list3: ${listEquals(list1, list3)}'); // false
}
Common Pitfalls
- Using
reduceon empty lists causesStateError - Modifying lists during iteration causes concurrent modification errors
- Forgetting that list methods often return new collections rather than modifying in place
- Not pre-allocating capacity for large lists, causing performance issues
Maps in Dart
Maps are collections of key-value pairs where each key is unique. Dart maps are similar to dictionaries in other languages and can use any object as a key.
Creating and Using Maps
void main() {
// Map creation
Map<String, int> ages = {
'Alice': 25,
'Bob': 30,
'Charlie': 35,
};
Map<String, dynamic> person = {
'name': 'John',
'age': 30,
'isStudent': false,
};
// Using Map constructor
Map<String, String> countries = Map();
countries['US'] = 'United States';
countries['UK'] = 'United Kingdom';
countries['CA'] = 'Canada';
// Map literals with different key types
Map<dynamic, dynamic> mixedKeys = {
'stringKey': 'value',
1: 'number key',
true: 'boolean key',
};
print('Ages: $ages');
print('Person: $person');
print('Countries: $countries');
print('Mixed keys: $mixedKeys');
// Map properties
print('Length: ${ages.length}');
print('Is empty: ${ages.isEmpty}');
print('Keys: ${ages.keys}');
print('Values: ${ages.values}');
print('Entries: ${ages.entries}');
}
Map Operations and Access
void main() {
Map<String, int> scores = {
'Alice': 95,
'Bob': 87,
'Charlie': 92,
};
// Accessing values
print('Alice score: ${scores['Alice']}'); // 95
print('David score: ${scores['David']}'); // null
// Safe access with default
print('David score: ${scores['David'] ?? 0}'); // 0
// Adding and updating
scores['David'] = 88; // Add new entry
scores['Alice'] = 96; // Update existing
scores.addAll({'Eve': 91, 'Frank': 89}); // Add multiple
print('After additions: $scores');
// Removing entries
scores.remove('Bob'); // Remove by key
scores.removeWhere((key, value) => value < 90); // Remove by condition
print('After removals: $scores');
// Checking existence
print('Contains Alice: ${scores.containsKey('Alice')}'); // true
print('Contains score 100: ${scores.containsValue(100)}'); // false
// Updating with callback
scores.update('Alice', (value) => value + 5); // Increment Alice's score
scores.update('Grace', (value) => value, ifAbsent: () => 85); // Add if absent
print('After updates: $scores');
}
Map Iteration and Transformation
void main() {
Map<String, int> inventory = {
'apples': 10,
'bananas': 20,
'oranges': 15,
'grapes': 25,
};
// Different ways to iterate
print('For each key-value:');
inventory.forEach((key, value) {
print(' $key: $value');
});
print('Using entries:');
for (var entry in inventory.entries) {
print(' ${entry.key}: ${entry.value}');
}
print('Using keys:');
for (var key in inventory.keys) {
print(' $key: ${inventory[key]}');
}
// Transformation methods
Map<String, int> doubled = Map.fromEntries(
inventory.entries.map((entry) =>
MapEntry(entry.key, entry.value * 2))
);
Map<String, String> descriptions = inventory.map((key, value) =>
MapEntry(key, 'There are $value $key in stock')
);
print('Doubled: $doubled');
print('Descriptions: $descriptions');
// Filtering
Map<String, int> highStock = Map.fromEntries(
inventory.entries.where((entry) => entry.value > 15)
);
print('High stock: $highStock');
}
Common Pitfalls
- Accessing non-existent keys returns
nullinstead of throwing error - Using mutable objects as keys can cause unpredictable behavior
- Forgetting that map iteration order is not guaranteed (use
LinkedHashMapfor order) - Not handling null values when working with map entries
Sets in Dart
Sets are unordered collections of unique items. Dart sets ensure that each element occurs only once and provide efficient membership testing.
Creating and Using Sets
void main() {
// Set creation
Set<String> fruits = {'apple', 'banana', 'orange'};
Set<int> numbers = {1, 2, 3, 4, 5};
var mixed = {1, 'hello', 3.14}; // Set<Object>
// Empty sets
Set<String> empty1 = {};
Set<String> empty2 = Set();
var empty3 = <String>{};
// From other collections
Set<int> fromList = Set.from([1, 2, 2, 3, 3, 3]); // Removes duplicates
Set<String> fromIterable = {'apple', 'banana'}.toSet();
print('Fruits: $fruits');
print('Numbers: $numbers');
print('Mixed: $mixed');
print('From list: $fromList'); // {1, 2, 3}
// Set properties
print('Length: ${fruits.length}');
print('Is empty: ${fruits.isEmpty}');
print('First: ${fruits.first}');
print('Contains apple: ${fruits.contains('apple')}');
}
Set Operations
void main() {
Set<int> setA = {1, 2, 3, 4, 5};
Set<int> setB = {4, 5, 6, 7, 8};
// Basic operations
setA.add(6); // Add single element
setA.addAll([7, 8, 9]); // Add multiple elements
setA.remove(3); // Remove element
setA.removeWhere((n) => n % 2 == 0); // Remove by condition
print('Modified setA: $setA');
// Set operations
print('Union: ${setA.union(setB)}'); // All elements from both
print('Intersection: ${setA.intersection(setB)}'); // Common elements
print('Difference: ${setA.difference(setB)}'); // In A but not in B
// Checking relationships
Set<int> subset = {1, 2};
Set<int> superset = {1, 2, 3, 4, 5};
print('subset is subset: ${subset.containsAll(subset)}'); // true
print('subset in superset: ${superset.containsAll(subset)}'); // true
print('setA intersects setB: ${setA.intersection(setB).isNotEmpty}'); // true
// Lookup (faster than lists)
Set<String> largeSet = Set.from(List.generate(10000, (i) => 'item$i'));
bool contains = largeSet.contains('item5000'); // Very fast
print('Contains item5000: $contains');
}
Set-Specific Methods
void main() {
Set<String> programmingLanguages = {
'Dart', 'JavaScript', 'Python', 'Java', 'C++'
};
Set<String> webLanguages = {
'Dart', 'JavaScript', 'TypeScript', 'PHP'
};
// Set-specific operations
Set<String> common = programmingLanguages.intersection(webLanguages);
Set<String> all = programmingLanguages.union(webLanguages);
Set<String> onlyProgramming = programmingLanguages.difference(webLanguages);
Set<String> onlyWeb = webLanguages.difference(programmingLanguages);
print('Common: $common'); // {Dart, JavaScript}
print('All: $all'); // All languages
print('Only programming: $onlyProgramming'); // {Python, Java, C++}
print('Only web: $onlyWeb'); // {TypeScript, PHP}
// Modifying sets with operations
programmingLanguages.addAll({'Ruby', 'Go'});
programmingLanguages.retainAll(webLanguages); // Keep only common elements
print('After retainAll: $programmingLanguages'); // {Dart, JavaScript}
// Set equality
Set<int> set1 = {1, 2, 3};
Set<int> set2 = {3, 2, 1}; // Same elements, different order
Set<int> set3 = {1, 2, 4};
print('set1 == set2: ${set1 == set2}'); // true (order doesn't matter)
print('set1 == set3: ${set1 == set3}'); // false
// Converting to list and back
List<int> listFromSet = set1.toList();
Set<int> setFromList = listFromSet.toSet();
print('List from set: $listFromSet');
print('Set from list: $setFromList');
}
Common Pitfalls
- Sets don't maintain insertion order (use
LinkedHashSetfor order) - Custom objects in sets need proper
hashCodeand==implementation - Set operations return new sets, they don't modify the original
- Confusing set literals
{}with map literals (context-dependent)
Enums in Dart
Enums (enumerations) are a special kind of class used to represent a fixed number of constant values. Enhanced enums in Dart can have fields, methods, and constructors.
Basic Enum Usage
// Simple enum
enum Color { red, green, blue }
// Enhanced enum with properties and methods
enum Status {
pending('Pending', 0),
approved('Approved', 1),
rejected('Rejected', 2);
final String displayName;
final int value;
const Status(this.displayName, this.value);
bool get isCompleted => this == Status.approved || this == Status.rejected;
String get formatted => '$displayName ($value)';
}
void main() {
// Basic enum operations
Color favoriteColor = Color.blue;
print('Favorite color: $favoriteColor');
print('Index: ${favoriteColor.index}');
print('Values: ${Color.values}');
// Switch with enums
switch (favoriteColor) {
case Color.red:
print('Red color selected');
break;
case Color.green:
print('Green color selected');
break;
case Color.blue:
print('Blue color selected');
break;
}
// Enhanced enum usage
Status currentStatus = Status.pending;
print('Status: ${currentStatus.displayName}');
print('Value: ${currentStatus.value}');
print('Is completed: ${currentStatus.isCompleted}');
print('Formatted: ${currentStatus.formatted}');
// Iterating through enum values
print('All status values:');
for (var status in Status.values) {
print(' ${status.name}: ${status.displayName}');
}
}
Advanced Enum Patterns
// Enum with factory constructor
enum PaymentMethod {
creditCard('Credit Card', 'cc', r'^\d{16}$'),
paypal('PayPal', 'pp', r'^[\w\.]+@[\w]+\.[\w]+$'),
bankTransfer('Bank Transfer', 'bt', r'^[A-Z]{2}\d{2}[\w]{12,32}$');
final String displayName;
final String code;
final String validationPattern;
const PaymentMethod(this.displayName, this.code, this.validationPattern);
bool validate(String input) {
return RegExp(validationPattern).hasMatch(input);
}
static PaymentMethod? fromCode(String code) {
try {
return PaymentMethod.values.firstWhere(
(method) => method.code == code
);
} catch (e) {
return null;
}
}
}
// Enum with mixins
mixin ComparableEnum on Enum {
int get value;
bool operator <(ComparableEnum other) => value < other.value;
bool operator <=(ComparableEnum other) => value <= other.value;
bool operator >(ComparableEnum other) => value > other.value;
bool operator >=(ComparableEnum other) => value >= other.value;
}
enum Priority with ComparableEnum {
low(1),
medium(2),
high(3),
critical(4);
final int value;
const Priority(this.value);
}
void main() {
// Payment method examples
PaymentMethod method = PaymentMethod.creditCard;
print('Method: ${method.displayName}');
print('Valid card: ${method.validate('4111111111111111')}');
// Factory constructor usage
PaymentMethod? fromCode = PaymentMethod.fromCode('pp');
print('From code: ${fromCode?.displayName}');
// Comparable enum usage
Priority p1 = Priority.medium;
Priority p2 = Priority.high;
print('p1 < p2: ${p1 < p2}'); // true
print('p1 >= p2: ${p1 >= p2}'); // false
// Sorting enum values
List<Priority> priorities = [Priority.high, Priority.low, Priority.critical];
priorities.sort();
print('Sorted priorities: $priorities');
}
Enums in Flutter Widgets
import 'package:flutter/material.dart';
enum ButtonVariant {
primary(Icons.check, Colors.blue),
secondary(Icons.add, Colors.grey),
danger(Icons.warning, Colors.red);
final IconData icon;
final Color color;
const ButtonVariant(this.icon, this.color);
}
class CustomButton extends StatelessWidget {
final ButtonVariant variant;
final String text;
final VoidCallback onPressed;
const CustomButton({
super.key,
required this.variant,
required this.text,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: variant.color,
foregroundColor: Colors.white,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(variant.icon),
const SizedBox(width: 8),
Text(text),
],
),
);
}
}
class EnumExample extends StatelessWidget {
const EnumExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
CustomButton(
variant: ButtonVariant.primary,
text: 'Save',
onPressed: () {},
),
CustomButton(
variant: ButtonVariant.secondary,
text: 'Add Item',
onPressed: () {},
),
CustomButton(
variant: ButtonVariant.danger,
text: 'Delete',
onPressed: () {},
),
],
);
}
}
Common Pitfalls
- Forgetting to handle all enum values in switch statements (use exhaustive checking)
- Not using enhanced enums when additional functionality is needed
- Storing complex state in enums instead of using them for simple categorization
- Not providing proper
toStringoverrides for better debugging
Classes in Dart
Classes are the foundation of object-oriented programming in Dart. They encapsulate data and behavior, supporting features like inheritance, interfaces, and mixins.
Basic Class Definition
class Person {
// Instance variables
String name;
int age;
String? email; // Nullable field
// Constructor
Person(this.name, this.age, [this.email]);
// Named constructor
Person.anonymous()
: name = 'Anonymous',
age = 0,
email = null;
// Getter
String get description => '$name, $age years old';
// Method
void sayHello() {
print('Hello, my name is $name');
}
// Override toString
@override
String toString() => 'Person(name: $name, age: $age)';
}
void main() {
// Creating objects
var person1 = Person('Alice', 25);
var person2 = Person('Bob', 30, 'bob@example.com');
var anonymous = Person.anonymous();
// Using objects
person1.sayHello();
print(person2.description);
print(anonymous);
// Accessing properties
person1.name = 'Alice Smith';
print('Updated name: ${person1.name}');
}
Advanced Class Features
class BankAccount {
// Private field (underscore prefix)
double _balance;
final String owner;
final String accountNumber;
// Constant constructor
static const String bankName = 'Dart Bank';
BankAccount(this.owner, this.accountNumber, [double initialBalance = 0])
: _balance = initialBalance;
// Getter for private field
double get balance => _balance;
// Methods
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
print('Deposited \$$amount. New balance: \$$_balance');
}
}
void withdraw(double amount) {
if (amount > 0 && amount <= _balance) {
_balance -= amount;
print('Withdrew \$$amount. New balance: \$$_balance');
} else {
print('Insufficient funds or invalid amount');
}
}
// Static method
static void printBankInfo() {
print('Welcome to $bankName');
}
// Operator overloading
BankAccount operator +(double amount) {
return BankAccount(owner, accountNumber, _balance + amount);
}
@override
bool operator ==(Object other) {
return other is BankAccount &&
other.accountNumber == accountNumber;
}
@override
int get hashCode => accountNumber.hashCode;
}
void main() {
var account = BankAccount('John Doe', '123456', 1000);
account.deposit(500);
account.withdraw(200);
// Using operator overloading
var newAccount = account + 1000;
print('New account balance: ${newAccount.balance}');
BankAccount.printBankInfo();
}
Inheritance and Polymorphism
// Base class
abstract class Shape {
final String color;
Shape(this.color);
// Abstract method (must be implemented by subclasses)
double area();
// Concrete method
void describe() {
print('This is a $color shape with area ${area()}');
}
}
// Subclass
class Rectangle extends Shape {
final double width;
final double height;
Rectangle(String color, this.width, this.height) : super(color);
@override
double area() => width * height;
@override
void describe() {
print('Rectangle: $width x $height, Color: $color');
}
}
// Another subclass
class Circle extends Shape {
final double radius;
Circle(String color, this.radius) : super(color);
@override
double area() => 3.14159 * radius * radius;
double get circumference => 2 * 3.14159 * radius;
}
void main() {
// Polymorphism in action
List<Shape> shapes = [
Rectangle('red', 10, 5),
Circle('blue', 7),
Rectangle('green', 8, 12),
];
for (var shape in shapes) {
shape.describe();
print('Area: ${shape.area()}');
print('---');
}
// Type checking and casting
for (var shape in shapes) {
if (shape is Circle) {
print('Circle circumference: ${shape.circumference}');
}
}
}
Common Pitfalls
- Forgetting to call
superin constructor when extending classes - Not marking fields as
finalwhen they shouldn't change - Overusing inheritance when composition would be better
- Not implementing
hashCodewhen overriding==
Inheritance in Dart
Inheritance allows classes to inherit properties and methods from other classes. Dart supports single inheritance but provides mixins for additional code reuse.
Basic Inheritance
class Animal {
String name;
int age;
Animal(this.name, this.age);
void eat() {
print('$name is eating');
}
void sleep() {
print('$name is sleeping');
}
void makeSound() {
print('$name makes a sound');
}
}
class Dog extends Animal {
String breed;
Dog(String name, int age, this.breed) : super(name, age);
void bark() {
print('$name barks: Woof! Woof!');
}
@override
void makeSound() {
bark();
}
void fetch() {
print('$name is fetching the ball');
}
}
class Cat extends Animal {
bool isIndoor;
Cat(String name, int age, this.isIndoor) : super(name, age);
void purr() {
print('$name purrs: Purrrrr...');
}
@override
void makeSound() {
purr();
}
void climb() {
print('$name is climbing a tree');
}
}
void main() {
var dog = Dog('Buddy', 3, 'Golden Retriever');
var cat = Cat('Whiskers', 2, true);
dog.eat(); // Inherited from Animal
dog.bark(); // Dog-specific
dog.makeSound(); // Overridden method
cat.sleep(); // Inherited from Animal
cat.purr(); // Cat-specific
cat.makeSound(); // Overridden method
// Polymorphism
List<Animal> animals = [dog, cat];
for (var animal in animals) {
animal.makeSound(); // Calls appropriate overridden method
}
}
Constructor Inheritance
class Vehicle {
final String make;
final String model;
final int year;
Vehicle(this.make, this.model, this.year);
// Named constructor
Vehicle.unknown()
: make = 'Unknown',
model = 'Unknown',
year = 2000;
void start() {
print('$make $model is starting');
}
void stop() {
print('$make $model is stopping');
}
}
class Car extends Vehicle {
final int doors;
final String fuelType;
// Calling super constructor
Car(String make, String model, int year, this.doors, this.fuelType)
: super(make, model, year);
// Named constructor that calls super named constructor
Car.unknown()
: doors = 4,
fuelType = 'Petrol',
super.unknown();
void honk() {
print('$make $model honks: Beep! Beep!');
}
@override
void start() {
print('Car $make $model with $fuelType engine is starting');
super.start(); // Call parent method
}
}
class ElectricCar extends Car {
final int batteryCapacity;
ElectricCar(String make, String model, int year, this.batteryCapacity)
: super(make, model, year, 4, 'Electric');
void charge() {
print('Charging $make $model with ${batteryCapacity}kWh battery');
}
@override
void start() {
print('Electric car starting silently...');
}
}
void main() {
var car = Car('Toyota', 'Camry', 2022, 4, 'Hybrid');
var electricCar = ElectricCar('Tesla', 'Model 3', 2023, 75);
var unknownCar = Car.unknown();
car.start();
car.honk();
electricCar.start();
electricCar.charge();
unknownCar.start();
}
Abstract Classes and Interfaces
// Abstract class (cannot be instantiated)
abstract class PaymentMethod {
String get name;
double get processingFee;
bool processPayment(double amount);
String getPaymentDetails();
}
// Interface implementation through abstract class
class CreditCard implements PaymentMethod {
final String cardNumber;
final String cardHolder;
final DateTime expiryDate;
CreditCard(this.cardNumber, this.cardHolder, this.expiryDate);
@override
String get name => 'Credit Card';
@override
double get processingFee => 0.02; // 2%
@override
bool processPayment(double amount) {
final total = amount + (amount * processingFee);
print('Processing credit card payment: \$$total');
return true;
}
@override
String getPaymentDetails() {
return 'Credit Card: ****${cardNumber.substring(cardNumber.length - 4)}';
}
}
class PayPal implements PaymentMethod {
final String email;
PayPal(this.email);
@override
String get name => 'PayPal';
@override
double get processingFee => 0.029; // 2.9%
@override
bool processPayment(double amount) {
final total = amount + (amount * processingFee);
print('Processing PayPal payment: \$$total from $email');
return true;
}
@override
String getPaymentDetails() {
return 'PayPal: $email';
}
}
void processOrder(List<PaymentMethod> methods, double amount) {
for (var method in methods) {
print('Using ${method.name}');
print('Details: ${method.getPaymentDetails()}');
method.processPayment(amount);
print('---');
}
}
void main() {
var payments = [
CreditCard('4111111111111111', 'John Doe', DateTime(2025, 12, 31)),
PayPal('john.doe@example.com'),
];
processOrder(payments, 100.0);
}
Common Pitfalls
- Creating deep inheritance hierarchies that are hard to maintain
- Forgetting to call
superin overridden methods when needed - Using inheritance for "is-a" relationships instead of "has-a" (use composition)
- Not making classes
abstractwhen they shouldn't be instantiated directly
Mixins in Dart
Mixins are a way of reusing code across multiple class hierarchies. They allow you to share functionality without using inheritance, solving the diamond problem.
Basic Mixin Usage
// Basic mixin
mixin Swimming {
void swim() {
print('Swimming in water');
}
}
mixin Flying {
void fly() {
print('Flying in the air');
}
}
mixin Running {
void run() {
print('Running on ground');
}
}
// Classes using mixins
class Duck with Swimming, Flying, Running {
void quack() {
print('Quack! Quack!');
}
}
class Fish with Swimming {
void bubble() {
print('Making bubbles');
}
}
class Eagle with Flying, Running {
void screech() {
print('Screech!');
}
}
void main() {
var duck = Duck();
duck.swim();
duck.fly();
duck.run();
duck.quack();
var fish = Fish();
fish.swim();
fish.bubble();
var eagle = Eagle();
eagle.fly();
eagle.run();
eagle.screech();
}
Mixin Constraints and Dependencies
// Mixin with constraints (on keyword)
class Animal {
String name;
Animal(this.name);
}
mixin Feeding on Animal {
void feed(String food) {
print('$name is eating $food');
}
}
mixin Grooming on Animal {
void groom() {
print('Grooming $name');
}
}
class Pet extends Animal with Feeding, Grooming {
Pet(String name) : super(name);
void play() {
print('$name is playing');
}
}
// Mixin with dependencies
mixin Logger {
void log(String message) {
print('LOG: $message');
}
}
mixin Timestamped {
DateTime get timestamp => DateTime.now();
}
class Application with Logger, Timestamped {
void start() {
log('Application started at $timestamp');
}
void stop() {
log('Application stopped at $timestamp');
}
}
void main() {
var pet = Pet('Fluffy');
pet.feed('kibble');
pet.groom();
pet.play();
var app = Application();
app.start();
Future.delayed(Duration(seconds: 1), app.stop);
}
Advanced Mixin Patterns
// Mixin with state and methods
mixin Cacheable {
final Map<String, dynamic> _cache = {};
void cache(String key, dynamic value) {
_cache[key] = value;
}
dynamic getFromCache(String key) => _cache[key];
bool containsKey(String key) => _cache.containsKey(key);
void clearCache() => _cache.clear();
}
mixin Validation {
bool isValidEmail(String email) {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}
bool isValidPhone(String phone) {
return RegExp(r'^[\d\-\+\s\(\)]{10,}$').hasMatch(phone);
}
bool isStrongPassword(String password) {
return password.length >= 8 &&
RegExp(r'[A-Z]').hasMatch(password) &&
RegExp(r'[a-z]').hasMatch(password) &&
RegExp(r'[0-9]').hasMatch(password);
}
}
// Abstract mixin
mixin Serialization {
Map<String, dynamic> toJson();
String toJsonString() {
return jsonEncode(toJson());
}
}
class User with Cacheable, Validation, Serialization {
final String name;
final String email;
final String phone;
User(this.name, this.email, this.phone);
bool validate() {
return isValidEmail(email) && isValidPhone(phone);
}
@override
Map<String, dynamic> toJson() {
return {
'name': name,
'email': email,
'phone': phone,
};
}
}
void main() {
var user = User('John Doe', 'john@example.com', '+1234567890');
// Using validation mixin
print('User valid: ${user.validate()}');
print('Strong password: ${user.isStrongPassword('Password123')}');
// Using cacheable mixin
user.cache('session', 'active');
print('Cache contains session: ${user.containsKey('session')}');
// Using serialization mixin
print('User JSON: ${user.toJsonString()}');
}
Mixins in Flutter Widgets
import 'package:flutter/material.dart';
// Mixin for animation functionality
mixin AnimationMixin on State<StatefulWidget> {
AnimationController? _controller;
Animation<double>? _animation;
void initializeAnimation(TickerProvider vsync) {
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: vsync,
);
_animation = CurvedAnimation(
parent: _controller!,
curve: Curves.easeInOut,
);
}
void startAnimation() {
_controller?.forward();
}
void reverseAnimation() {
_controller?.reverse();
}
Animation<double>? get animation => _animation;
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
}
// Mixin for theme functionality
mixin ThemeMixin on Widget {
ThemeData getLightTheme() {
return ThemeData.light().copyWith(
primaryColor: Colors.blue,
colorScheme: ColorScheme.fromSwatch().copyWith(secondary: Colors.orange),
);
}
ThemeData getDarkTheme() {
return ThemeData.dark().copyWith(
primaryColor: Colors.blueGrey,
colorScheme: ColorScheme.fromSwatch().copyWith(secondary: Colors.amber),
);
}
}
class AnimatedButton extends StatefulWidget with ThemeMixin {
final String text;
final VoidCallback onPressed;
const AnimatedButton({
super.key,
required this.text,
required this.onPressed,
});
@override
State<AnimatedButton> createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton>
with AnimationMixin, TickerProviderStateMixin {
@override
void initState() {
super.initState();
initializeAnimation(this);
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: animation!,
child: ElevatedButton(
onPressed: () {
startAnimation();
widget.onPressed();
},
child: Text(widget.text),
),
);
}
}
Common Pitfalls
- Using mixins for complex state management instead of simple behavior reuse
- Creating mixins with too many responsibilities (violating single responsibility)
- Not understanding the linearization order of mixins
- Using mixins when simple composition would be clearer
Variables in Dart
Variables in Dart are used to store and manage data. Dart supports type inference and provides different ways to declare variables based on their mutability and scope.
Variable Declaration and Types
void main() {
// Explicit type declaration
String name = 'Alice';
int age = 25;
double height = 5.9;
bool isStudent = true;
// Type inference with var
var city = 'New York';
var population = 8419000;
var temperature = 72.5;
var isSunny = true;
// Dynamic type (use sparingly)
dynamic anything = 'hello';
anything = 42;
anything = true;
// Final variables (runtime constants)
final currentYear = DateTime.now().year;
final username = 'alice123';
// Const variables (compile-time constants)
const double pi = 3.14159;
const int daysInWeek = 7;
const String appName = 'MyApp';
print('Name: $name');
print('Age: $age');
print('City: $city');
print('Current year: $currentYear');
print('PI: $pi');
}
Variable Scope and Lifetime
// Global variable (avoid when possible)
int globalCounter = 0;
class Counter {
// Instance variable
int count = 0;
// Static variable (class-level)
static int totalInstances = 0;
Counter() {
totalInstances++;
}
void increment() {
// Local variable
int localIncrement = 1;
count += localIncrement;
globalCounter += localIncrement;
}
void demonstrateScope() {
int localVar = 10;
if (true) {
int blockVar = 20; // Only accessible in this block
print('Block variable: $blockVar');
print('Local variable: $localVar');
print('Instance variable: $count');
print('Global variable: $globalCounter');
}
// print(blockVar); // Error: blockVar not accessible here
print('Local variable: $localVar');
}
}
void demonstrateVariableLifetime() {
int functionScoped = 42;
void nestedFunction() {
int nestedScoped = 100;
print('Function scoped: $functionScoped');
print('Nested scoped: $nestedScoped');
}
nestedFunction();
// print(nestedScoped); // Error: nestedScoped not accessible here
}
void main() {
var counter1 = Counter();
var counter2 = Counter();
counter1.increment();
counter1.increment();
counter2.increment();
print('Counter 1: ${counter1.count}');
print('Counter 2: ${counter2.count}');
print('Global counter: $globalCounter');
print('Total instances: ${Counter.totalInstances}');
counter1.demonstrateScope();
demonstrateVariableLifetime();
}
Late Variables and Lazy Initialization
class DatabaseService {
// Late initialization - will be initialized before use
late final String _connectionString;
void initialize(String connectionString) {
_connectionString = connectionString;
}
void connect() {
print('Connecting to: $_connectionString');
}
}
class Configuration {
// Late final with lazy initialization
late final String apiKey = _loadApiKey();
String _loadApiKey() {
print('Loading API key...');
return 'secret-api-key-12345';
}
}
class ExpensiveObject {
final String name;
ExpensiveObject(this.name) {
print('Creating expensive object: $name');
}
void use() {
print('Using expensive object: $name');
}
}
class Service {
// Lazy initialization - only created when first accessed
late final ExpensiveObject _expensiveObject = ExpensiveObject('Database');
void doWork() {
print('Starting work...');
_expensiveObject.use(); // Object created here
}
}
void main() {
// Late variable demonstration
var dbService = DatabaseService();
dbService.initialize('server=localhost;database=test');
dbService.connect();
// Lazy initialization demonstration
var config = Configuration();
print('Configuration created');
print('API Key: ${config.apiKey}'); // Loaded here
var service = Service();
print('Service created');
service.doWork(); // Expensive object created here
}
Common Pitfalls
- Using
varwhen the type isn't clear from the initializer - Accessing
latevariables before they're initialized - Overusing global variables instead of proper state management
- Not using
finalfor variables that don't change
Null Safety in Dart
Null safety prevents null reference exceptions by distinguishing between nullable and non-nullable types. It's a core feature of Dart that makes code more robust.
Nullable vs Non-nullable Types
void main() {
// Non-nullable types (cannot be null)
String name = 'Alice';
int age = 25;
List<String> fruits = ['apple', 'banana'];
// These would cause compile errors:
// String nullName = null; // Error
// int nullAge = null; // Error
// List<String> nullList = null; // Error
// Nullable types (can be null)
String? nullableName;
int? nullableAge;
List<String>? nullableList;
// Valid assignments
nullableName = 'Bob';
nullableName = null;
nullableAge = 30;
nullableAge = null;
nullableList = ['orange'];
nullableList = null;
print('Nullable name: $nullableName');
print('Nullable age: $nullableAge');
print('Nullable list: $nullableList');
// Working with nullable types
if (nullableName != null) {
print('Name length: ${nullableName.length}'); // Safe to access
}
// Null-aware operators
String displayName = nullableName ?? 'Guest';
int displayAge = nullableAge ?? 0;
List<String> displayList = nullableList ?? [];
print('Display name: $displayName');
print('Display age: $displayAge');
print('Display list: $displayList');
}
Null Safety in Practice
class User {
final String name;
final int age;
final String? email; // Optional field
final String? phone; // Optional field
User({
required this.name,
required this.age,
this.email,
this.phone,
});
// Factory constructor for nullable creation
factory User.fromJson(Map<String, dynamic> json) {
final name = json['name'];
final age = json['age'];
if (name is! String || age is! int) {
throw ArgumentError('Invalid user data');
}
return User(
name: name,
age: age,
email: json['email'] as String?,
phone: json['phone'] as String?,
);
}
String get contactInfo {
if (email != null && phone != null) {
return 'Email: $email, Phone: $phone';
} else if (email != null) {
return 'Email: $email';
} else if (phone != null) {
return 'Phone: $phone';
} else {
return 'No contact info';
}
}
void sendNotification(String message) {
// Safe method calls with null-aware operator
email?.contains('@'); // Only called if email is not null
// Using bang operator when sure it's not null
if (email != null) {
print('Sending email to $email: $message');
} else if (phone != null) {
print('Sending SMS to $phone: $message');
} else {
print('Cannot send notification: no contact info');
}
}
}
void processUser(User? user) {
// Early return for null
if (user == null) {
print('No user provided');
return;
}
// User is now promoted to non-nullable
print('Processing user: ${user.name}');
print('Contact: ${user.contactInfo}');
user.sendNotification('Hello!');
}
void main() {
var user1 = User(name: 'Alice', age: 25, email: 'alice@example.com');
var user2 = User(name: 'Bob', age: 30); // No contact info
User? user3 = null; // Nullable user
processUser(user1);
processUser(user2);
processUser(user3);
// Working with lists of nullable types
List<String?> names = ['Alice', null, 'Bob', null, 'Charlie'];
// Filter out nulls
var nonNullNames = names.whereType<String>().toList();
print('Non-null names: $nonNullNames');
// Handle nulls in operations
var nameLengths = names.map((name) => name?.length ?? 0).toList();
print('Name lengths: $nameLengths');
}
Advanced Null Safety Patterns
// Generic classes with null safety
class Response<T> {
final T? data;
final String? error;
final bool success;
Response.success(this.data)
: success = true,
error = null;
Response.error(this.error)
: success = false,
data = null;
T get requireData {
if (data == null) {
throw StateError('No data available');
}
return data!;
}
void handle({
required void Function(T data) onSuccess,
required void Function(String error) onError,
}) {
if (success) {
onSuccess(requireData);
} else {
onError(error!);
}
}
}
// Null safety with extension methods
extension NullableStringExtensions on String? {
bool get isNullOrEmpty => this == null || this!.isEmpty;
String orDefault(String defaultValue) => this ?? defaultValue;
String? toUpperCaseOrNull() => this?.toUpperCase();
}
extension NullableListExtensions on List? {
bool get isNullOrEmpty => this == null || this!.isEmpty;
int get safeLength => this?.length ?? 0;
List<T> orEmpty<T>() => (this as List<T>?) ?? <T>[];
}
void main() {
// Using generic response
var successResponse = Response.success('Hello, World!');
var errorResponse = Response.error('Something went wrong');
successResponse.handle(
onSuccess: (data) => print('Success: $data'),
onError: (error) => print('Error: $error'),
);
errorResponse.handle(
onSuccess: (data) => print('Success: $data'),
onError: (error) => print('Error: $error'),
);
// Using extension methods
String? nullableString = null;
print('Is null or empty: ${nullableString.isNullOrEmpty}');
print('With default: ${nullableString.orDefault('default')}');
print('Upper case: ${nullableString.toUpperCaseOrNull()}');
List<int>? nullableList = null;
print('List length: ${nullableList.safeLength}');
print('List: ${nullableList.orEmpty()}');
}
Common Pitfalls
- Overusing the bang operator (
!) instead of proper null checking - Not handling null cases in function parameters and return types
- Forgetting that collections can contain nullable elements
- Not using null-aware operators when working with nullable types
Conditional Statements: if, else if, else
Conditional statements allow your Flutter app to make decisions and execute different code paths based on conditions. Dart provides if, else if, and else statements for control flow.
Basic Conditional Statements
void main() {
int number = 10;
// Simple if statement
if (number > 0) {
print('The number is positive');
}
// if-else statement
if (number % 2 == 0) {
print('The number is even');
} else {
print('The number is odd');
}
// if-else if-else chain
int score = 85;
if (score >= 90) {
print('Grade: A');
} else if (score >= 80) {
print('Grade: B');
} else if (score >= 70) {
print('Grade: C');
} else if (score >= 60) {
print('Grade: D');
} else {
print('Grade: F');
}
// Ternary operator
String result = score >= 60 ? 'Pass' : 'Fail';
print('Result: $result');
// Null-aware ternary
String? name;
String displayName = name != null ? name : 'Guest';
String displayName2 = name ?? 'Guest'; // Same as above
print('Welcome, $displayName');
}
Conditional Statements in Flutter Widgets
import 'package:flutter/material.dart';
class ConditionalWidget extends StatelessWidget {
final bool isLoggedIn;
final String? userName;
final int notificationCount;
const ConditionalWidget({
super.key,
required this.isLoggedIn,
this.userName,
required this.notificationCount,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
// Conditional rendering with if statement
if (isLoggedIn)
Text('Welcome back, $userName!', style: TextStyle(fontSize: 20))
else
const Text('Please log in', style: TextStyle(fontSize: 20)),
const SizedBox(height: 20),
// Conditional rendering with ternary operator
Container(
padding: const EdgeInsets.all(16),
color: isLoggedIn ? Colors.green[100] : Colors.red[100],
child: Row(
children: [
Icon(
isLoggedIn ? Icons.check_circle : Icons.error,
color: isLoggedIn ? Colors.green : Colors.red,
),
const SizedBox(width: 8),
Text(isLoggedIn ? 'Logged In' : 'Logged Out'),
],
),
),
const SizedBox(height: 20),
// Complex conditional with else-if logic
if (notificationCount == 0)
const Text('No new notifications')
else if (notificationCount == 1)
const Text('You have 1 new notification')
else
Text('You have $notificationCount new notifications'),
const SizedBox(height: 20),
// Conditional with multiple conditions
if (isLoggedIn && notificationCount > 0)
ElevatedButton(
onPressed: () {},
child: const Text('View Notifications'),
),
],
);
}
}
class UserProfile extends StatelessWidget {
final User? user;
const UserProfile({super.key, this.user});
@override
Widget build(BuildContext context) {
return Column(
children: [
// Safe null handling
if (user != null) ...[
CircleAvatar(
backgroundImage: NetworkImage(user!.avatarUrl),
radius: 40,
),
const SizedBox(height: 8),
Text(user!.name, style: const TextStyle(fontSize: 18)),
Text(user!.email, style: const TextStyle(color: Colors.grey)),
] else ...[
const CircleAvatar(
child: Icon(Icons.person),
radius: 40,
),
const SizedBox(height: 8),
const Text('Guest User', style: TextStyle(fontSize: 18)),
const Text('Please log in', style: TextStyle(color: Colors.grey)),
],
const SizedBox(height: 20),
// Conditional button state
ElevatedButton(
onPressed: user != null ? () {
// Handle user action
} : null,
child: const Text('Edit Profile'),
),
],
);
}
}
class User {
final String name;
final String email;
final String avatarUrl;
User({required this.name, required this.email, required this.avatarUrl});
}
Advanced Conditional Patterns
void main() {
// Switch statements (alternative to if-else chains)
String grade = 'B';
switch (grade) {
case 'A':
print('Excellent!');
break;
case 'B':
print('Good job!');
break;
case 'C':
print('Fair');
break;
case 'D':
print('Needs improvement');
break;
case 'F':
print('Failed');
break;
default:
print('Invalid grade');
}
// Switch expressions (Dart 3.0+)
String message = switch (grade) {
'A' => 'Excellent!',
'B' => 'Good job!',
'C' => 'Fair',
'D' => 'Needs improvement',
'F' => 'Failed',
_ => 'Invalid grade',
};
print(message);
// Pattern matching with switch
var shape = Rectangle(10, 5);
var area = switch (shape) {
Square(side: var s) => s * s,
Rectangle(width: var w, height: var h) => w * h,
Circle(radius: var r) => 3.14159 * r * r,
};
print('Area: $area');
// Guard clauses
int? value = 10;
if (value != null && value > 5) {
print('Value is not null and greater than 5');
}
// Early returns
String? validateInput(String? input) {
if (input == null || input.isEmpty) {
return 'Input cannot be empty';
}
if (input.length < 3) {
return 'Input must be at least 3 characters';
}
if (!input.contains('@')) {
return 'Input must contain @ symbol';
}
return null; // No errors
}
String? error = validateInput('test@example.com');
if (error != null) {
print('Validation error: $error');
} else {
print('Input is valid');
}
}
// Example classes for pattern matching
sealed class Shape {}
class Square implements Shape { final double side; Square(this.side); }
class Rectangle implements Shape {
final double width, height;
Rectangle(this.width, this.height);
}
class Circle implements Shape { final double radius; Circle(this.radius); }
Common Pitfalls
- Using assignment (
=) instead of equality (==) in conditions - Forgetting to handle all cases in switch statements
- Not using null-aware operators when working with nullable values
- Creating deeply nested if-else statements that are hard to read
for Loop in Dart
The for loop is used when you know how many times you want to iterate. Dart provides traditional for loops, for-in loops, and forEach methods for different use cases.
Basic for Loops
void main() {
// Traditional for loop
print('Count from 1 to 5:');
for (int i = 1; i <= 5; i++) {
print(' Count: $i');
}
// Countdown
print('Countdown from 10:');
for (int i = 10; i >= 1; i--) {
print(' $i');
}
print('Liftoff! 🚀');
// Iterating through lists
List<String> fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];
print('Fruits list:');
for (int i = 0; i < fruits.length; i++) {
print(' ${i + 1}. ${fruits[i]}');
}
// for-in loop (simpler for collections)
print('Fruits using for-in:');
for (String fruit in fruits) {
print(' - $fruit');
}
// forEach method (functional approach)
print('Fruits using forEach:');
fruits.forEach((fruit) {
print(' • $fruit');
});
// One-line forEach
fruits.forEach((fruit) => print(' → $fruit'));
}
Advanced for Loop Patterns
void main() {
// Multiple loop variables
for (int i = 0, j = 10; i < j; i++, j--) {
print('i: $i, j: $j');
}
// Skipping iterations with continue
print('Odd numbers from 1 to 10:');
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
print(' $i');
}
// Breaking out of loops
List<int> numbers = [2, 4, 6, 7, 8, 10];
int? firstOdd;
for (int number in numbers) {
if (number % 2 != 0) {
firstOdd = number;
break; // Stop searching once found
}
}
print('First odd number: $firstOdd');
// Nested loops
print('Multiplication table:');
for (int i = 1; i <= 5; i++) {
String row = '';
for (int j = 1; j <= 5; j++) {
row += '${i * j}\t';
}
print(' $row');
}
// Looping with steps
print('Even numbers from 0 to 10:');
for (int i = 0; i <= 10; i += 2) {
print(' $i');
}
// Labeled breaks (rarely needed)
outer: for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
print('Breaking both loops at i=$i, j=$j');
break outer;
}
print(' i=$i, j=$j, product=${i * j}');
}
}
}
for Loops in Flutter Widgets
import 'package:flutter/material.dart';
class ProductList extends StatelessWidget {
final List<Product> products;
const ProductList({super.key, required this.products});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Products', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
// Using for loop with spread operator to create widgets
...products.map((product) => ProductCard(product: product)).toList(),
const SizedBox(height: 16),
// Alternative: Using ListView.builder for better performance
SizedBox(
height: 200,
child: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(product: products[index]);
},
),
),
],
);
}
}
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue[100],
child: Text('\$${product.price}'),
),
title: Text(product.name),
subtitle: Text(product.category),
trailing: Icon(
product.isAvailable ? Icons.check_circle : Icons.cancel,
color: product.isAvailable ? Colors.green : Colors.red,
),
),
);
}
}
class DynamicGrid extends StatelessWidget {
final int itemCount;
const DynamicGrid({super.key, required this.itemCount});
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: itemCount,
itemBuilder: (context, index) {
return Container(
color: Colors.blue[(100 + (index * 100)) % 900],
child: Center(
child: Text(
'Item ${index + 1}',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
),
);
},
);
}
}
class Product {
final String name;
final String category;
final double price;
final bool isAvailable;
Product({
required this.name,
required this.category,
required this.price,
required this.isAvailable,
});
}
void main() {
// Example data
var products = [
Product(name: 'Laptop', category: 'Electronics', price: 999.99, isAvailable: true),
Product(name: 'Book', category: 'Education', price: 29.99, isAvailable: true),
Product(name: 'Headphones', category: 'Electronics', price: 149.99, isAvailable: false),
Product(name: 'Water Bottle', category: 'Sports', price: 19.99, isAvailable: true),
];
// This would be used in a Flutter app
// runApp(MaterialApp(home: Scaffold(body: ProductList(products: products))));
}
Common Pitfalls
- Using traditional for loops when for-in or forEach would be more readable
- Modifying lists while iterating over them
- Not using
ListView.builderfor long lists in Flutter - Infinite loops when the termination condition is never met
while and do-while Loops in Dart
while loops repeat a block of code as long as a condition is true, while do-while loops guarantee at least one execution. These are useful when you don't know how many iterations are needed.
Basic while and do-while Loops
void main() {
// Basic while loop
int count = 1;
while (count <= 5) {
print('Count: $count');
count++;
}
// do-while loop (executes at least once)
int number;
do {
number = DateTime.now().microsecond % 10;
print('Generated number: $number');
} while (number != 5);
// User input simulation
print('--- User Input Simulation ---');
simulateUserInput();
// File reading simulation
print('--- File Reading Simulation ---');
simulateFileReading();
// Game loop simulation
print('--- Game Loop Simulation ---');
simulateGameLoop();
}
void simulateUserInput() {
// Simulating user input validation
String? input;
int attempts = 0;
while (input != 'quit' && attempts < 3) {
// In real app, this would be actual user input
input = attempts == 0 ? 'invalid' :
attempts == 1 ? 'also invalid' : 'quit';
attempts++;
print('Attempt $attempts: User entered "$input"');
if (input == 'quit') {
print('Goodbye!');
} else if (attempts >= 3) {
print('Too many attempts. Please try again later.');
}
}
}
void simulateFileReading() {
// Simulating reading lines from a file
List<String> lines = [
'Line 1: Hello World',
'Line 2: Dart Programming',
'Line 3: Flutter Development',
'EOF'
];
int index = 0;
String line = lines[index];
while (line != 'EOF') {
print('Reading: $line');
index++;
line = lines[index];
}
print('End of file reached');
}
void simulateGameLoop() {
// Simple game loop simulation
int playerHealth = 100;
int enemyHealth = 80;
int round = 1;
while (playerHealth > 0 && enemyHealth > 0) {
print('\n--- Round $round ---');
// Player attack
int playerDamage = (DateTime.now().microsecond % 20) + 10;
enemyHealth -= playerDamage;
print('Player attacks for $playerDamage damage. Enemy health: $enemyHealth');
if (enemyHealth <= 0) {
print('Player wins!');
break;
}
// Enemy attack
int enemyDamage = (DateTime.now().microsecond % 15) + 5;
playerHealth -= enemyDamage;
print('Enemy attacks for $enemyDamage damage. Player health: $playerHealth');
if (playerHealth <= 0) {
print('Enemy wins!');
break;
}
round++;
// Safety check to prevent infinite loops
if (round > 10) {
print('Game ended in a draw!');
break;
}
}
}
Advanced while Loop Patterns
void main() {
// Processing collections with while
processQueue();
// Retry mechanism with exponential backoff
simulateApiCallWithRetry();
// Data streaming simulation
simulateDataStream();
}
void processQueue() {
// Simulating a queue processing system
List<String> queue = ['task1', 'task2', 'task3', 'task4', 'task5'];
int processedCount = 0;
print('Processing queue...');
while (queue.isNotEmpty) {
String task = queue.removeAt(0);
print('Processing: $task');
processedCount++;
// Simulate processing time
// In real app, this might be async work
if (processedCount >= 3) {
print('Reached processing limit for this batch');
break;
}
}
print('Queue processing completed. Remaining tasks: ${queue.length}');
}
void simulateApiCallWithRetry() {
// Simulating API call with retry logic
int maxRetries = 3;
int retryCount = 0;
bool success = false;
while (!success && retryCount < maxRetries) {
retryCount++;
print('Attempt $retryCount: Calling API...');
// Simulate API call (would be async in real app)
bool apiCallSuccessful = retryCount == maxRetries; // Succeeds on last try
if (apiCallSuccessful) {
success = true;
print('API call successful!');
} else {
print('API call failed. Retrying...');
// Exponential backoff
int delay = (1 << retryCount) * 1000; // 2^retryCount seconds
print('Waiting ${delay}ms before retry...');
// In real app: await Future.delayed(Duration(milliseconds: delay));
}
}
if (!success) {
print('All retry attempts failed');
}
}
void simulateDataStream() {
// Simulating data stream processing
List<int> dataStream = List.generate(10, (i) => i * 10);
int bytesProcessed = 0;
const int chunkSize = 3;
print('Processing data stream...');
while (bytesProcessed < dataStream.length) {
int endIndex = bytesProcessed + chunkSize;
if (endIndex > dataStream.length) {
endIndex = dataStream.length;
}
List<int> chunk = dataStream.sublist(bytesProcessed, endIndex);
print('Processing chunk: $chunk');
bytesProcessed += chunk.length;
// Simulate processing delay
// In real app: await Future.delayed(Duration(milliseconds: 100));
}
print('Data stream processing completed. Total bytes: $bytesProcessed');
}
while Loops in Flutter
import 'package:flutter/material.dart';
class LoadingScreen extends StatefulWidget {
const LoadingScreen({super.key});
@override
State<LoadingScreen> createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
double _progress = 0.0;
bool _isLoading = true;
@override
void initState() {
super.initState();
_simulateLoading();
}
void _simulateLoading() async {
// Simulate a loading process
while (_progress < 1.0) {
await Future.delayed(const Duration(milliseconds: 200));
setState(() {
_progress += 0.1;
});
// Safety check to prevent infinite loop
if (_progress > 1.0) {
_progress = 1.0;
}
}
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Loading Screen')),
body: Center(
child: _isLoading
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 20),
Text('Loading... ${(_progress * 100).toStringAsFixed(0)}%'),
const SizedBox(height: 10),
LinearProgressIndicator(value: _progress),
],
)
: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle, color: Colors.green, size: 64),
SizedBox(height: 20),
Text('Loading Complete!', style: TextStyle(fontSize: 24)),
],
),
),
);
}
}
class PaginatedList extends StatefulWidget {
const PaginatedList({super.key});
@override
State<PaginatedList> createState() => _PaginatedListState();
}
class _PaginatedListState extends State<PaginatedList> {
final List<String> _items = [];
bool _isLoading = false;
bool _hasMore = true;
int _page = 0;
@override
void initState() {
super.initState();
_loadMoreItems();
}
Future<void> _loadMoreItems() async {
if (_isLoading || !_hasMore) return;
setState(() {
_isLoading = true;
});
// Simulate API call delay
await Future.delayed(const Duration(seconds: 1));
// Simulate paginated data
List<String> newItems = List.generate(
10,
(i) => 'Item ${_page * 10 + i + 1}'
);
setState(() {
_items.addAll(newItems);
_page++;
_isLoading = false;
_hasMore = _page < 3; // Only 3 pages for demo
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Paginated List')),
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo is ScrollEndNotification &&
scrollInfo.metrics.extentAfter == 0) {
_loadMoreItems();
}
return false;
},
child: ListView.builder(
itemCount: _items.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= _items.length) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
);
}
return ListTile(title: Text(_items[index]));
},
),
),
);
}
}
Common Pitfalls
- Infinite loops when the termination condition is never met
- Not updating the loop control variable within the loop body
- Using while loops when for loops would be more appropriate
- Blocking the main thread with long-running while loops
Iterables in Dart
Iterables are lazy-evaluated collections that can be iterated over. Unlike Lists, Iterables don't store all elements in memory at once, making them efficient for large datasets.
Basic Iterable Operations
void main() {
// Creating iterables
Iterable<int> numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map((x) => x * 2);
var evenNumbers = numbers.where((x) => x % 2 == 0);
print('Numbers: $numbers');
print('Doubled: $doubled');
print('Even numbers: $evenNumbers');
// Iterable properties
print('First: ${numbers.first}');
print('Last: ${numbers.last}');
print('Length: ${numbers.length}');
print('Is empty: ${numbers.isEmpty}');
// Lazy evaluation demonstration
print('--- Lazy Evaluation ---');
var lazyNumbers = numbers.map((x) {
print('Processing: $x');
return x * 2;
});
print('Iterable created, but no processing yet');
print('First element: ${lazyNumbers.first}'); // Only processes first element
print('Converting to list:');
var list = lazyNumbers.toList(); // Processes all elements
}
Advanced Iterable Methods
void main() {
Iterable<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Chaining operations
var result = numbers
.where((x) => x > 3)
.map((x) => x * x)
.where((x) => x < 50);
print('Chained operations: $result');
// Take and skip
print('First 3: ${numbers.take(3).toList()}');
print('Skip first 3: ${numbers.skip(3).toList()}');
print('Take while < 5: ${numbers.takeWhile((x) => x < 5).toList()}');
print('Skip while < 5: ${numbers.skipWhile((x) => x < 5).toList()}');
// Searching
print('First > 5: ${numbers.firstWhere((x) => x > 5)}');
print('Last < 8: ${numbers.lastWhere((x) => x < 8)}');
print('Single = 5: ${numbers.singleWhere((x) => x == 5)}');
// Checking conditions
print('Any > 10: ${numbers.any((x) => x > 10)}');
print('All > 0: ${numbers.every((x) => x > 0)}');
print('Contains 7: ${numbers.contains(7)}');
// Reduction
print('Sum: ${numbers.reduce((a, b) => a + b)}');
print('Max: ${numbers.reduce((a, b) => a > b ? a : b)}');
print('Min: ${numbers.reduce((a, b) => a < b ? a : b)}');
// Folding (more flexible than reduce)
print('Sum with fold: ${numbers.fold(0, (a, b) => a + b)}');
print('Product with fold: ${numbers.fold(1, (a, b) => a * b)}');
print('String concatenation: ${numbers.fold('', (a, b) => '$a$b')}');
}
Custom Iterables and Generators
void main() {
// Custom iterable
var countdown = CountdownIterable(5);
print('Countdown:');
for (var number in countdown) {
print(' $number');
}
// Synchronous generator
print('Fibonacci sequence:');
var fibonacci = generateFibonacci(10);
for (var number in fibonacci) {
print(' $number');
}
// Using iterable in algorithms
var data = generateData(1000000); // 1 million items
var processed = data
.where((x) => x % 2 == 0)
.map((x) => x * 2)
.take(10);
print('First 10 processed items: ${processed.toList()}');
}
// Custom iterable class
class CountdownIterable extends Iterable<int> {
final int start;
CountdownIterable(this.start);
@override
Iterator<int> get iterator => CountdownIterator(start);
}
class CountdownIterator implements Iterator<int> {
int _current;
final int _start;
CountdownIterator(this._start) : _current = _start + 1;
@override
int get current => _current;
@override
bool moveNext() {
_current--;
return _current >= 0;
}
}
// Synchronous generator function
Iterable<int> generateFibonacci(int count) sync* {
int a = 0, b = 1;
for (int i = 0; i < count; i++) {
yield a;
int next = a + b;
a = b;
b = next;
}
}
// Generator for large datasets
Iterable<int> generateData(int count) sync* {
for (int i = 0; i < count; i++) {
yield i;
}
}
Common Pitfalls
- Multiple iterations over the same iterable may cause recomputation
- Forgetting that iterables are lazy and need to be converted to lists for multiple uses
- Using
reduceon empty iterables causes errors - Not understanding the performance implications of lazy evaluation
Loop Control Statements
Loop control statements like break, continue, and labels allow you to control the flow of loops more precisely.
Break and Continue
void main() {
// Break statement
print('Break example:');
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // Exit loop completely
}
print(' $i');
}
// Continue statement
print('Continue example:');
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
print(' $i');
}
// Break in while loop
print('While loop with break:');
int count = 0;
while (true) {
count++;
if (count > 5) {
break;
}
print(' Count: $count');
}
// Continue in nested loops
print('Nested loops with continue:');
outer: for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j == 4) {
continue outer; // Continue outer loop
}
print(' i=$i, j=$j, product=${i * j}');
}
}
// Practical examples
searchExample();
validationExample();
}
void searchExample() {
print('--- Search Example ---');
List<String> names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve'];
String searchName = 'Charlie';
for (String name in names) {
if (name == searchName) {
print('Found $searchName!');
break; // Stop searching once found
}
print('Checking: $name');
}
}
void validationExample() {
print('--- Validation Example ---');
List<int> numbers = [1, 2, -3, 4, 5, -6, 7, 8];
for (int number in numbers) {
if (number < 0) {
print('Invalid number found: $number. Skipping...');
continue; // Skip negative numbers
}
// Process valid numbers
int squared = number * number;
print('Processing $number: squared = $squared');
}
}
Labels and Advanced Control Flow
void main() {
// Labeled break
print('Labeled break:');
outer: for (int i = 1; i <= 3; i++) {
inner: for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
print('Breaking both loops at i=$i, j=$j');
break outer;
}
print(' i=$i, j=$j');
}
}
// Labeled continue
print('Labeled continue:');
outer: for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i == 2 && j == 2) {
print('Skipping i=2, j=2');
continue outer;
}
print(' i=$i, j=$j');
}
}
// Matrix search example
matrixSearch();
// Data processing with early termination
dataProcessing();
}
void matrixSearch() {
print('--- Matrix Search ---');
List<List<int>> matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
int target = 5;
search: for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == target) {
print('Found $target at position [$i][$j]');
break search;
}
}
}
}
void dataProcessing() {
print('--- Data Processing ---');
List<Map<String, dynamic>> users = [
{'name': 'Alice', 'age': 25, 'active': true},
{'name': 'Bob', 'age': 30, 'active': false},
{'name': 'Charlie', 'age': 35, 'active': true},
{'name': 'David', 'age': 40, 'active': false},
];
int activeCount = 0;
for (var user in users) {
if (!user['active']) {
print('Skipping inactive user: ${user['name']}');
continue;
}
activeCount++;
print('Processing active user: ${user['name']}');
// Simulate some processing
if (activeCount >= 2) {
print('Reached processing limit for this batch');
break;
}
}
print('Total active users processed: $activeCount');
}
Loop Control in Flutter
import 'package:flutter/material.dart';
class SearchableList extends StatefulWidget {
const SearchableList({super.key});
@override
State<SearchableList> createState() => _SearchableListState();
}
class _SearchableListState extends State<SearchableList> {
final List<String> _items = List.generate(100, (i) => 'Item ${i + 1}');
final TextEditingController _searchController = TextEditingController();
List<String> _filteredItems = [];
@override
void initState() {
super.initState();
_filteredItems = _items;
_searchController.addListener(_filterItems);
}
void _filterItems() {
final query = _searchController.text.toLowerCase();
if (query.isEmpty) {
setState(() {
_filteredItems = _items;
});
return;
}
List<String> results = [];
// Using break for efficient searching
for (String item in _items) {
if (item.toLowerCase().contains(query)) {
results.add(item);
// Limit results for performance
if (results.length >= 20) {
results.add('... and more');
break;
}
}
}
setState(() {
_filteredItems = results;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Searchable List')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: _searchController,
decoration: const InputDecoration(
labelText: 'Search',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
),
),
Expanded(
child: ListView.builder(
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
// Skip rendering for continuation indicator
if (item == '... and more') {
return const ListTile(
title: Text('... and more', style: TextStyle(fontStyle: FontStyle.italic)),
);
}
return ListTile(title: Text(item));
},
),
),
],
),
);
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
}
class PaginationWidget extends StatefulWidget {
const PaginationWidget({super.key});
@override
State<PaginationWidget> createState() => _PaginationWidgetState();
}
class _PaginationWidgetState extends State<PaginationWidget> {
int _currentPage = 1;
final int _totalPages = 10;
final int _itemsPerPage = 5;
List<String> _getCurrentPageItems() {
List<String> items = [];
int startIndex = (_currentPage - 1) * _itemsPerPage;
for (int i = startIndex; i < startIndex + _itemsPerPage; i++) {
if (i >= 50) break; // Safety check
items.add('Item ${i + 1}');
}
return items;
}
void _nextPage() {
if (_currentPage < _totalPages) {
setState(() {
_currentPage++;
});
}
}
void _previousPage() {
if (_currentPage > 1) {
setState(() {
_currentPage--;
});
}
}
@override
Widget build(BuildContext context) {
final currentItems = _getCurrentPageItems();
return Scaffold(
appBar: AppBar(title: const Text('Pagination Example')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: currentItems.length,
itemBuilder: (context, index) => ListTile(
title: Text(currentItems[index]),
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: _currentPage > 1 ? _previousPage : null,
child: const Text('Previous'),
),
Text('Page $_currentPage of $_totalPages'),
ElevatedButton(
onPressed: _currentPage < _totalPages ? _nextPage : null,
child: const Text('Next'),
),
],
),
),
],
),
);
}
}
Common Pitfalls
- Overusing labels can make code harder to read and maintain
- Using
breakwhencontinueis more appropriate - Forgetting that
breakonly exits the innermost loop without labels - Creating infinite loops without proper break conditions
Functions in Dart
Functions are reusable blocks of code that perform specific tasks. Dart supports first-class functions, meaning functions can be assigned to variables, passed as arguments, and returned from other functions.
Basic Function Syntax
// Function declaration
int add(int a, int b) {
return a + b;
}
// Arrow syntax for single expression
int multiply(int a, int b) => a * b;
// Function with optional parameters
String greet(String name, [String? title]) {
if (title != null) {
return 'Hello, $title $name!';
}
return 'Hello, $name!';
}
// Function with named parameters
void printUser({String? name, int? age, String? email}) {
print('Name: $name');
print('Age: $age');
print('Email: $email');
}
// Function with default values
void showMessage(String message, {bool isError = false}) {
if (isError) {
print('ERROR: $message');
} else {
print('INFO: $message');
}
}
void main() {
// Calling functions
print('Addition: ${add(5, 3)}');
print('Multiplication: ${multiply(4, 7)}');
print('Greeting: ${greet('Alice')}');
print('Formal greeting: ${greet('Bob', 'Mr.')}');
// Named parameters
printUser(name: 'Charlie', age: 30, email: 'charlie@example.com');
printUser(name: 'David'); // Other parameters are null
// Default values
showMessage('Everything is working');
showMessage('Something went wrong', isError: true);
// First-class functions
var operation = add;
print('Using function variable: ${operation(10, 20)}');
// Higher-order functions
processNumbers(5, 3, add);
processNumbers(5, 3, multiply);
}
// Higher-order function
void processNumbers(int a, int b, int Function(int, int) operation) {
final result = operation(a, b);
print('Processing $a and $b: $result');
}
Advanced Function Features
void main() {
// Anonymous functions (lambdas)
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map((x) => x * 2);
var even = numbers.where((x) {
return x % 2 == 0;
});
print('Doubled: $doubled');
print('Even: $even');
// Closures
var counter = createCounter();
print('Counter: ${counter()}');
print('Counter: ${counter()}');
print('Counter: ${counter()}');
// Function composition
var process = compose(toUpperCase, exclaim);
print('Composed: ${process('hello')}');
// Currying and partial application
var addFive = curryAdd(5);
print('Add five to 10: ${addFive(10)}');
// Recursive functions
print('Factorial of 5: ${factorial(5)}');
print('Fibonacci of 10: ${fibonacci(10)}');
}
// Closure example
Function createCounter() {
int count = 0;
return () => ++count;
}
// Function composition
String toUpperCase(String text) => text.toUpperCase();
String exclaim(String text) => '$text!';
String Function(String) compose(
String Function(String) f,
String Function(String) g
) {
return (x) => g(f(x));
}
// Currying example
int Function(int) curryAdd(int a) {
return (int b) => a + b;
}
// Recursive functions
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Generic functions
T identity<T>(T value) => value;
T first<T>(List<T> list) => list.first;
// Function overloading with optional parameters
class Calculator {
int sum(int a, int b) => a + b;
double sumDouble(double a, double b) => a + b;
// Can't have true overloading, but can use optional parameters
num sumDynamic(num a, num b) => a + b;
}
Functions in Flutter
import 'package:flutter/material.dart';
class FunctionExamples extends StatelessWidget {
const FunctionExamples({super.key});
// Helper functions for widget building
Widget _buildHeader(String title) {
return Text(
title,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
);
}
Widget _buildButton(String text, VoidCallback onPressed) {
return ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
List<Widget> _buildItemList(List<String> items) {
return items.map((item) => ListTile(title: Text(item))).toList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Function Examples')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildHeader('Function Examples in Flutter'),
const SizedBox(height: 20),
// Using functions as callbacks
_buildButton('Say Hello', () {
_showSnackBar(context, 'Hello, Flutter!');
}),
const SizedBox(height: 10),
_buildButton('Calculate', () {
final result = _calculateSum(5, 3);
_showSnackBar(context, 'Result: $result');
}),
const SizedBox(height: 20),
_buildHeader('Dynamic List'),
// Using function to build dynamic content
..._buildItemList(['Apple', 'Banana', 'Orange', 'Grape']),
const SizedBox(height: 20),
_buildHeader('Conditional Widgets'),
// Function with conditional logic
_buildConditionalWidget(true),
_buildConditionalWidget(false),
],
),
);
}
Widget _buildConditionalWidget(bool isActive) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
isActive ? Icons.check_circle : Icons.cancel,
color: isActive ? Colors.green : Colors.red,
),
const SizedBox(width: 10),
Text(isActive ? 'Active' : 'Inactive'),
],
),
),
);
}
void _showSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
int _calculateSum(int a, int b) => a + b;
}
// Higher-order widget function
Widget withPadding(Widget child) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: child,
);
}
Widget withCard(Widget child) {
return Card(
child: child,
);
}
// Composing widget functions
Widget createStyledContainer(Widget child) {
return withCard(withPadding(child));
}
class FunctionalWidget extends StatelessWidget {
final String title;
final Widget Function(BuildContext) builder;
const FunctionalWidget({
super.key,
required this.title,
required this.builder,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
builder(context),
],
);
}
}
Common Pitfalls
- Forgetting to return values from non-void functions
- Using too many positional parameters instead of named parameters
- Creating functions that are too long and do too many things
- Not using type annotations for function parameters and return types
Async Programming in Dart
Asynchronous programming allows your app to perform non-blocking operations. Dart uses Future and Stream with async/await syntax for clean asynchronous code.
Futures and Async/Await
void main() async {
print('Starting async operations...');
// Basic async/await
await basicAsyncExample();
// Error handling in async code
await errorHandlingExample();
// Parallel execution
await parallelExecutionExample();
print('All async operations completed!');
}
Future<void> basicAsyncExample() async {
print('--- Basic Async Example ---');
// Simulating network request
String data = await fetchData();
print('Fetched data: $data');
// Simulating file operation
String processed = await processData(data);
print('Processed data: $processed');
// Chaining async operations
String result = await fetchData()
.then((data) => processData(data))
.then((processed) => 'Final: $processed');
print('Chained result: $result');
}
Future<void> errorHandlingExample() async {
print('--- Error Handling Example ---');
try {
String data = await fetchDataWithError();
print('Success: $data');
} catch (e) {
print('Error caught: $e');
}
// Using catchError
await fetchDataWithError()
.then((data) => print('Data: $data'))
.catchError((error) => print('Error: $error'));
}
Future<void> parallelExecutionExample() async {
print('--- Parallel Execution Example ---');
// Sequential execution (slow)
var start = DateTime.now();
var result1 = await fetchDelayedData(1, 1000);
var result2 = await fetchDelayedData(2, 1000);
var result3 = await fetchDelayedData(3, 1000);
var sequentialTime = DateTime.now().difference(start);
print('Sequential time: $sequentialTime');
// Parallel execution (fast)
start = DateTime.now();
var futures = [
fetchDelayedData(4, 1000),
fetchDelayedData(5, 1000),
fetchDelayedData(6, 1000),
];
var results = await Future.wait(futures);
var parallelTime = DateTime.now().difference(start);
print('Parallel results: $results');
print('Parallel time: $parallelTime');
}
// Simulated async functions
Future<String> fetchData() async {
await Future.delayed(Duration(milliseconds: 500));
return 'Sample Data';
}
Future<String> processData(String data) async {
await Future.delayed(Duration(milliseconds: 300));
return data.toUpperCase();
}
Future<String> fetchDataWithError() async {
await Future.delayed(Duration(milliseconds: 200));
throw Exception('Network error occurred');
}
Future<String> fetchDelayedData(int id, int delay) async {
await Future.delayed(Duration(milliseconds: delay));
return 'Data $id';
}
Streams and Reactive Programming
void main() async {
print('--- Stream Examples ---');
// Basic stream usage
await basicStreamExample();
// Stream controllers
await streamControllerExample();
// Stream transformations
await streamTransformationExample();
}
Future<void> basicStreamExample() async {
print('Basic Stream:');
var stream = countStream(5);
await for (int value in stream) {
print(' Received: $value');
}
}
Future<void> streamControllerExample() async {
print('Stream Controller:');
var controller = StreamController<String>();
var stream = controller.stream;
// Listen to the stream
var subscription = stream.listen(
(data) => print(' Data: $data'),
onError: (error) => print(' Error: $error'),
onDone: () => print(' Stream closed'),
);
// Add data to the stream
controller.add('Hello');
controller.add('Stream');
controller.addError('Test error');
controller.add('World');
// Close the stream
await controller.close();
}
Future<void> streamTransformationExample() async {
print('Stream Transformations:');
var numberStream = numberGenerator(10);
// Transformations
var doubled = numberStream.map((x) => x * 2);
var even = numberStream.where((x) => x % 2 == 0);
var sum = numberStream.fold(0, (a, b) => a + b);
print(' Doubled values:');
await for (var value in doubled) {
print(' $value');
}
// Create new stream for even numbers
var evenStream = numberGenerator(10).where((x) => x % 2 == 0);
print(' Even values:');
await for (var value in evenStream) {
print(' $value');
}
}
// Stream generator
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(milliseconds: 200));
yield i;
}
}
Stream<int> numberGenerator(int count) async* {
for (int i = 1; i <= count; i++) {
await Future.delayed(Duration(milliseconds: 100));
yield i;
}
}
Async Programming in Flutter
import 'package:flutter/material.dart';
import 'dart:convert';
class AsyncFlutterExample extends StatefulWidget {
const AsyncFlutterExample({super.key});
@override
State<AsyncFlutterExample> createState() => _AsyncFlutterExampleState();
}
class _AsyncFlutterExampleState extends State<AsyncFlutterExample> {
String _data = 'Loading...';
bool _isLoading = false;
List<String> _items = [];
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
setState(() {
_isLoading = true;
});
try {
// Simulate network request
final data = await _fetchDataFromApi();
setState(() {
_data = data;
_isLoading = false;
});
} catch (e) {
setState(() {
_data = 'Error: $e';
_isLoading = false;
});
}
}
Future<void> _loadItems() async {
setState(() {
_isLoading = true;
});
try {
// Simulate loading multiple items
var futures = List.generate(5, (i) => _fetchItem(i + 1));
var results = await Future.wait(futures);
setState(() {
_items = results;
_isLoading = false;
});
} catch (e) {
setState(() {
_data = 'Error loading items: $e';
_isLoading = false;
});
}
}
Future<String> _fetchDataFromApi() async {
await Future.delayed(const Duration(seconds: 2));
return 'Data loaded successfully!';
}
Future<String> _fetchItem(int id) async {
await Future.delayed(Duration(milliseconds: 500 * id));
return 'Item $id';
}
void _refreshData() {
_loadData();
_loadItems();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Async Flutter Example'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _isLoading ? null : _refreshData,
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_isLoading)
const LinearProgressIndicator()
else
const SizedBox(height: 4),
const SizedBox(height: 16),
Text(
_data,
style: TextStyle(
color: _data.startsWith('Error') ? Colors.red : Colors.green,
fontSize: 18,
),
),
const SizedBox(height: 20),
const Text(
'Loaded Items:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
if (_items.isEmpty && !_isLoading)
const Text('No items loaded')
else
Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => ListTile(
title: Text(_items[index]),
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _isLoading ? null : _loadItems,
child: const Icon(Icons.add),
),
);
}
}
class StreamBuilderExample extends StatelessWidget {
const StreamBuilderExample({super.key});
Stream<int> _numberStream() async* {
for (int i = 1; i <= 10; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('StreamBuilder Example')),
body: Center(
child: StreamBuilder<int>(
stream: _numberStream(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text('No stream connected');
case ConnectionState.waiting:
return const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Waiting for stream...'),
],
);
case ConnectionState.active:
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${snapshot.data}',
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
const Text('Active stream data'),
],
);
case ConnectionState.done:
return const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle, color: Colors.green, size: 64),
SizedBox(height: 16),
Text('Stream completed!'),
],
);
}
},
),
),
);
}
}
Common Pitfalls
- Forgetting to use
awaiton async function calls - Not handling errors in async operations with try-catch
- Calling setState after dispose in async callbacks
- Creating memory leaks by not canceling Stream subscriptions
Imports in Dart
Imports allow you to use code from other libraries in your Dart application. Dart provides several ways to import libraries and manage namespaces.
Basic Import Syntax
// Importing core Dart libraries
import 'dart:math';
import 'dart:convert';
import 'dart:async';
// Importing package libraries
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// Importing local files
import 'utils/helpers.dart';
import 'models/user.dart';
import 'services/api_service.dart';
void main() {
// Using imported libraries
print('Pi: ${pi}'); // From dart:math
print('JSON encoded: ${jsonEncode({'name': 'John'})}'); // From dart:convert
// Using package imports with prefix
var client = http.Client();
// Using local imports
var user = User('Alice', 25);
print('User: ${user.name}');
}
Import Types and Aliases
// Standard import
import 'dart:math';
// Import with prefix (avoids name conflicts)
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:google_sign_in/google_sign_in.dart' as google;
// Import only specific members
import 'package:flutter/material.dart' show MaterialApp, Scaffold, AppBar;
import 'package:flutter/services.dart' hide FileIO;
// Import all members except specific ones
import 'utils/helpers.dart' hide deprecatedFunction;
class AuthService {
void signIn() {
// Using prefixed imports
var firebaseUser = firebase.FirebaseAuth.instance.currentUser;
var googleSignIn = google.GoogleSignIn();
// Using specific imports
var app = MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
),
);
}
}
// Re-exporting libraries
library my_package;
export 'src/models.dart';
export 'src/services.dart';
export 'src/utils.dart';
Library Organization and Best Practices
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:my_app/config/constants.dart';
import 'package:my_app/models/user.dart';
import 'package:my_app/services/api.dart';
import 'package:my_app/utils/validators.dart';
import 'package:my_app/widgets/header.dart';
import 'package:my_app/widgets/footer.dart';
// lib/models/user.dart
library user_model;
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final String name;
final String email;
User(this.name, this.email);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// lib/services/api.dart
library api_service;
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/user.dart';
import '../config/constants.dart';
class ApiService {
static const String baseUrl = AppConstants.apiUrl;
Future<User> fetchUser(String id) async {
final response = await http.get(Uri.parse('$baseUrl/users/$id'));
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load user');
}
}
}
// Conditional imports for different platforms
import 'src/http_io.dart'
if (dart.library.html) 'src/http_web.dart'
as http_impl;
class HttpService {
void makeRequest() {
http_impl.makeHttpRequest(); // Uses platform-specific implementation
}
}
Common Pitfalls
- Circular imports between files causing compilation errors
- Not using prefixes when importing libraries with conflicting names
- Importing entire libraries when only specific members are needed
- Forgetting to add dependencies to pubspec.yaml for package imports
Widgets Introduction
Widgets are the building blocks of Flutter applications. Everything in Flutter is a widget, from structural elements to styling and animation.
What are Widgets?
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Widgets Introduction',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Widgets Demo'),
actions: [
IconButton(icon: const Icon(Icons.search), onPressed: () {}),
IconButton(icon: const Icon(Icons.settings), onPressed: () {}),
],
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Hello, Flutter!', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
Icon(Icons.flutter_dash, size: 50, color: Colors.blue),
SizedBox(height: 20),
ElevatedButton(
onPressed: null,
child: Text('Click Me'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.business), label: 'Business'),
BottomNavigationBarItem(icon: Icon(Icons.school), label: 'School'),
],
),
);
}
}
Widget Tree and Composition
class WidgetCompositionExample extends StatelessWidget {
const WidgetCompositionExample({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Header section
Row(
children: [
const CircleAvatar(
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
radius: 20,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('John Doe', style: Theme.of(context).textTheme.titleMedium),
Text('2 hours ago', style: Theme.of(context).textTheme.bodySmall),
],
),
),
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
const SizedBox(height: 16),
// Content section
Text('This is a post about Flutter widgets and how they compose to build beautiful UIs.',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 12),
// Image
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
'https://example.com/sample.jpg',
height: 200,
width: double.infinity,
fit: BoxFit.cover,
),
),
const SizedBox(height: 12),
// Actions section
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
IconButton(icon: const Icon(Icons.favorite_border), onPressed: () {}),
const Text('42'),
],
),
Row(
children: [
IconButton(icon: const Icon(Icons.comment), onPressed: () {}),
const Text('7'),
],
),
IconButton(icon: const Icon(Icons.share), onPressed: () {}),
],
),
],
),
);
}
}
Widget Lifecycle and Key Concepts
class WidgetLifecycleExample extends StatefulWidget {
const WidgetLifecycleExample({super.key});
@override
State<WidgetLifecycleExample> createState() => _WidgetLifecycleExampleState();
}
class _WidgetLifecycleExampleState extends State<WidgetLifecycleExample>
with WidgetsBindingObserver {
int _counter = 0;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
print('Widget initialized');
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('Dependencies changed');
}
@override
void didUpdateWidget(WidgetLifecycleExample oldWidget) {
super.didUpdateWidget(oldWidget);
print('Widget updated');
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
print('Widget disposed');
super.dispose();
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
print('Building widget');
return Scaffold(
appBar: AppBar(title: const Text('Widget Lifecycle')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text('$_counter', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 20),
const Text('Check console for lifecycle messages'),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
Common Pitfalls
- Forgetting to call super in lifecycle methods
- Creating widgets in build methods instead of storing them as variables
- Not using const constructors for static widgets
- Overusing setState instead of more efficient state management
Stateless Widgets
Stateless widgets are immutable widgets that describe part of the user interface which can depend on configuration information but doesn't require mutable state.
Creating Stateless Widgets
import 'package:flutter/material.dart';
// Basic stateless widget
class MyText extends StatelessWidget {
final String text;
final double fontSize;
final Color color;
const MyText({
super.key,
required this.text,
this.fontSize = 16,
this.color = Colors.black,
});
@override
Widget build(BuildContext context) {
return Text(
text,
style: TextStyle(fontSize: fontSize, color: color),
);
}
}
// Composite stateless widget
class UserProfile extends StatelessWidget {
final String name;
final String email;
final String avatarUrl;
final bool isVerified;
const UserProfile({
super.key,
required this.name,
required this.email,
required this.avatarUrl,
this.isVerified = false,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Stack(
children: [
CircleAvatar(
backgroundImage: NetworkImage(avatarUrl),
radius: 30,
),
if (isVerified)
Positioned(
bottom: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.verified, color: Colors.blue, size: 16),
),
),
],
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(email, style: TextStyle(color: Colors.grey[600])),
],
),
),
IconButton(
icon: const Icon(Icons.message),
onPressed: () {
// Handle message action
},
),
],
),
);
}
}
// Using BuildContext
class ThemeAwareWidget extends StatelessWidget {
const ThemeAwareWidget({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
return Container(
padding: const EdgeInsets.all(16),
color: isDark ? Colors.grey[800] : Colors.grey[200],
child: Text(
'Current theme: ${isDark ? 'Dark' : 'Light'}',
style: theme.textTheme.bodyLarge?.copyWith(
color: isDark ? Colors.white : Colors.black,
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Column(
children: [
const MyText(text: 'Hello World', fontSize: 24, color: Colors.blue),
UserProfile(
name: 'Alice Johnson',
email: 'alice@example.com',
avatarUrl: 'https://example.com/avatar1.jpg',
isVerified: true,
),
const ThemeAwareWidget(),
],
),
),
));
}
Best Practices for Stateless Widgets
// Good: Using const constructor
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isPrimary;
const CustomButton({
super.key,
required this.text,
required this.onPressed,
this.isPrimary = false,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: isPrimary
? ElevatedButton.styleFrom(backgroundColor: Colors.blue)
: ElevatedButton.styleFrom(backgroundColor: Colors.grey),
onPressed: onPressed,
child: Text(text),
);
}
}
// Good: Breaking down complex widgets
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildImage(),
_buildContent(),
_buildActions(),
],
),
);
}
Widget _buildImage() {
return Image.network(
product.imageUrl,
height: 150,
width: double.infinity,
fit: BoxFit.cover,
);
}
Widget _buildContent() {
return Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.name, style: const TextStyle(fontWeight: FontWeight.bold)),
Text('\$${product.price}', style: const TextStyle(color: Colors.green)),
Text(product.description, maxLines: 2, overflow: TextOverflow.ellipsis),
],
),
);
}
Widget _buildActions() {
return ButtonBar(
children: [
IconButton(icon: const Icon(Icons.favorite_border), onPressed: () {}),
ElevatedButton(onPressed: () {}, child: const Text('Add to Cart')),
],
);
}
}
class Product {
final String name;
final double price;
final String description;
final String imageUrl;
const Product({
required this.name,
required this.price,
required this.description,
required this.imageUrl,
});
}
Common Pitfalls
- Trying to modify final fields in stateless widgets
- Creating widgets that should be stateful as stateless
- Not using const constructors for widgets that don't change
- Over-nesting widgets instead of creating separate widget classes
Stateful Widgets
Stateful widgets maintain state that might change during the lifetime of the widget. Use stateful widgets when the UI needs to change dynamically in response to user interactions or data changes.
Creating Stateful Widgets
import 'package:flutter/material.dart';
// Basic stateful widget
class Counter extends StatefulWidget {
final int initialValue;
const Counter({super.key, this.initialValue = 0});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
void initState() {
super.initState();
_count = widget.initialValue;
}
void _increment() {
setState(() {
_count++;
});
}
void _decrement() {
setState(() {
_count--;
});
}
void _reset() {
setState(() {
_count = 0;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $_count', style: const TextStyle(fontSize: 24)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: _decrement, child: const Text('-')),
const SizedBox(width: 20),
ElevatedButton(onPressed: _increment, child: const Text('+')),
const SizedBox(width: 20),
OutlinedButton(onPressed: _reset, child: const Text('Reset')),
],
),
],
);
}
}
// Stateful widget with complex state
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
bool _obscurePassword = true;
String? _errorMessage;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _submitForm() async {
if (!_formKey.currentState!.validate()) return;
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
// Handle successful login
if (_emailController.text == 'test@example.com' &&
_passwordController.text == 'password') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Login successful!')),
);
} else {
setState(() {
_errorMessage = 'Invalid credentials';
});
}
} catch (e) {
setState(() {
_errorMessage = 'Login failed: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
void _togglePasswordVisibility() {
setState(() {
_obscurePassword = !_obscurePassword;
});
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off),
onPressed: _togglePasswordVisibility,
),
),
obscureText: _obscurePassword,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
if (_errorMessage != null) ...[
const SizedBox(height: 16),
Text(
_errorMessage!,
style: const TextStyle(color: Colors.red),
),
],
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading ? null : _submitForm,
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Login'),
),
),
],
),
),
);
}
}
State Management Patterns
// State with multiple related values
class UserPreferences extends StatefulWidget {
const UserPreferences({super.key});
@override
State createState() => _UserPreferencesState();
}
class _UserPreferencesState extends State {
String _theme = 'light';
String _language = 'en';
bool _notifications = true;
bool _darkMode = false;
void _updateTheme(String theme) {
setState(() {
_theme = theme;
});
}
void _updateLanguage(String language) {
setState(() {
_language = language;
});
}
void _toggleNotifications() {
setState(() {
_notifications = !_notifications;
});
}
void _toggleDarkMode() {
setState(() {
_darkMode = !_darkMode;
});
}
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
_buildSectionHeader('Appearance'),
_buildThemeSelector(),
_buildDarkModeSwitch(),
_buildSectionHeader('Language'),
_buildLanguageSelector(),
_buildSectionHeader('Notifications'),
_buildNotificationSwitch(),
_buildSectionHeader('Current Settings'),
_buildCurrentSettings(),
],
);
}
Widget _buildSectionHeader(String title) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
);
}
Widget _buildThemeSelector() {
return SegmentedButton(
segments: const [
ButtonSegment(value: 'light', label: Text('Light')),
ButtonSegment(value: 'dark', label: Text('Dark')),
ButtonSegment(value: 'auto', label: Text('Auto')),
],
selected: {_theme},
onSelectionChanged: (Set newSelection) {
_updateTheme(newSelection.first);
},
);
}
Widget _buildLanguageSelector() {
return DropdownButtonFormField(
value: _language,
items: const [
DropdownMenuItem(value: 'en', child: Text('English')),
DropdownMenuItem(value: 'es', child: Text('Spanish')),
DropdownMenuItem(value: 'fr', child: Text('French')),
],
onChanged: (value) {
if (value != null) _updateLanguage(value);
},
);
}
Widget _buildNotificationSwitch() {
return SwitchListTile(
title: const Text('Enable Notifications'),
value: _notifications,
onChanged: (value) => _toggleNotifications(),
);
}
Widget _buildDarkModeSwitch() {
return SwitchListTile(
title: const Text('Dark Mode'),
value: _darkMode,
onChanged: (value) => _toggleDarkMode(),
);
}
Widget _buildCurrentSettings() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Theme: $_theme'),
Text('Language: $_language'),
Text('Notifications: ${_notifications ? 'On' : 'Off'}'),
Text('Dark Mode: ${_darkMode ? 'On' : 'Off'}'),
],
),
),
);
}
}
Common Pitfalls
- Calling setState unnecessarily, causing unnecessary rebuilds
- Not disposing controllers and listeners in dispose method
- Storing state that should be managed higher up in the widget tree
- Mutating state directly without calling setState
Layout Widgets in Flutter
Layout widgets help you arrange other widgets in your Flutter app. Flutter provides a rich set of layout widgets for creating responsive and beautiful UIs.
Basic Layout Widgets
import 'package:flutter/material.dart';
class BasicLayouts extends StatelessWidget {
const BasicLayouts({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Basic Layouts')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildContainerExample(),
const SizedBox(height: 20),
_buildRowExample(),
const SizedBox(height: 20),
_buildColumnExample(),
const SizedBox(height: 20),
_buildStackExample(),
const SizedBox(height: 20),
_buildExpandedExample(),
],
),
),
);
}
Widget _buildContainerExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Container', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue),
),
child: const Text('This is a container with padding and decoration'),
),
],
);
}
Widget _buildRowExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Row', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(color: Colors.red, height: 50, width: 50),
Container(color: Colors.green, height: 50, width: 50),
Container(color: Colors.blue, height: 50, width: 50),
],
),
],
);
}
Widget _buildColumnExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Column', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Column(
children: [
Container(color: Colors.red, height: 50, width: double.infinity),
Container(color: Colors.green, height: 50, width: double.infinity),
Container(color: Colors.blue, height: 50, width: double.infinity),
],
),
],
);
}
Widget _buildStackExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Stack', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
SizedBox(
height: 100,
child: Stack(
children: [
Container(color: Colors.blue[200], width: double.infinity, height: 80),
Positioned(
left: 20,
top: 10,
child: Container(color: Colors.red, width: 60, height: 60),
),
Positioned(
right: 20,
bottom: 10,
child: Container(color: Colors.green, width: 40, height: 40),
),
],
),
),
],
);
}
Widget _buildExpandedExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Expanded', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Row(
children: [
Expanded(
flex: 2,
child: Container(color: Colors.red, height: 50),
),
Expanded(
flex: 1,
child: Container(color: Colors.green, height: 50),
),
Expanded(
flex: 1,
child: Container(color: Colors.blue, height: 50),
),
],
),
],
);
}
}
Advanced Layout Widgets
class AdvancedLayouts extends StatelessWidget {
const AdvancedLayouts({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Advanced Layouts')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildGridViewExample(),
const SizedBox(height: 20),
_buildListViewExample(),
const SizedBox(height: 20),
_buildCustomScrollViewExample(),
const SizedBox(height: 20),
_buildLayoutBuilderExample(),
],
),
),
);
}
Widget _buildGridViewExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('GridView', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
SizedBox(
height: 200,
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1,
),
itemCount: 9,
itemBuilder: (context, index) => Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('Item ${index + 1}')),
),
),
),
],
);
}
Widget _buildListViewExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('ListView', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
SizedBox(
height: 150,
child: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) => ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('List Item ${index + 1}'),
subtitle: Text('Subtitle for item ${index + 1}'),
trailing: const Icon(Icons.arrow_forward),
),
),
),
],
);
}
Widget _buildCustomScrollViewExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('CustomScrollView', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
SizedBox(
height: 200,
child: CustomScrollView(
slivers: [
SliverAppBar(
title: const Text('Sliver App Bar'),
floating: true,
expandedHeight: 100,
flexibleSpace: FlexibleSpaceBar(
background: Container(color: Colors.blue),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Item $index')),
childCount: 20,
),
),
],
),
),
],
);
}
Widget _buildLayoutBuilderExample() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('LayoutBuilder', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Container(
height: 100,
color: Colors.grey[200],
child: LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
final height = constraints.maxHeight;
return Container(
padding: const EdgeInsets.all(8),
child: Text(
'Available space: ${width.toStringAsFixed(1)} x ${height.toStringAsFixed(1)}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
},
),
),
],
);
}
}
Responsive Layout Patterns
class ResponsiveLayout extends StatelessWidget {
const ResponsiveLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Responsive Layout')),
body: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return _buildTabletLayout();
} else {
return _buildMobileLayout();
}
},
),
);
}
Widget _buildMobileLayout() {
return ListView(
children: [
_buildHeader(),
_buildContent(),
_buildFooter(),
],
);
}
Widget _buildTabletLayout() {
return Row(
children: [
Expanded(
flex: 1,
child: _buildSidebar(),
),
Expanded(
flex: 3,
child: Column(
children: [
_buildHeader(),
Expanded(child: _buildContent()),
_buildFooter(),
],
),
),
],
);
}
Widget _buildHeader() {
return Container(
height: 80,
color: Colors.blue,
child: const Center(child: Text('Header', style: TextStyle(color: Colors.white))),
);
}
Widget _buildSidebar() {
return Container(
color: Colors.grey[200],
child: ListView(
children: [
ListTile(title: const Text('Menu Item 1'), leading: const Icon(Icons.home)),
ListTile(title: const Text('Menu Item 2'), leading: const Icon(Icons.settings)),
ListTile(title: const Text('Menu Item 3'), leading: const Icon(Icons.person)),
],
),
);
}
Widget _buildContent() {
return Padding(
padding: const EdgeInsets.all(16),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1.5,
),
itemCount: 6,
itemBuilder: (context, index) => Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.widgets, size: 40, color: Colors.primaries[index]),
const SizedBox(height: 8),
Text('Card ${index + 1}'),
],
),
),
),
),
);
}
Widget _buildFooter() {
return Container(
height: 60,
color: Colors.grey[800],
child: const Center(child: Text('Footer', style: TextStyle(color: Colors.white))),
);
}
}
Common Pitfalls
- Using fixed sizes instead of flexible layouts
- Not considering different screen sizes and orientations
- Over-nesting widgets causing performance issues
- Forgetting to use SingleChildScrollView for scrollable content
setState in Flutter
setState is the most basic form of state management in Flutter. It tells the framework that the internal state of a State object has changed and the widget needs to be rebuilt.
Basic setState Usage
import 'package:flutter/material.dart';
class CounterApp extends StatefulWidget {
const CounterApp({super.key});
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int _counter = 0;
String _message = 'Welcome!';
void _incrementCounter() {
setState(() {
_counter++;
_message = 'Counter incremented to $_counter';
});
}
void _decrementCounter() {
setState(() {
_counter--;
_message = 'Counter decremented to $_counter';
});
}
void _resetCounter() {
setState(() {
_counter = 0;
_message = 'Counter reset to 0';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('setState Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_message, style: const TextStyle(fontSize: 18)),
const SizedBox(height: 20),
Text('$_counter', style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _decrementCounter,
child: const Text('-'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: _resetCounter,
child: const Text('Reset'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: _incrementCounter,
child: const Text('+'),
),
],
),
],
),
),
);
}
}
Advanced setState Patterns
class TodoApp extends StatefulWidget {
const TodoApp({super.key});
@override
State<TodoApp> createState() => _TodoAppState();
}
class _TodoAppState extends State<TodoApp> {
final List<TodoItem> _todos = [];
final TextEditingController _textController = TextEditingController();
void _addTodo() {
final text = _textController.text.trim();
if (text.isEmpty) return;
setState(() {
_todos.add(TodoItem(
id: DateTime.now().millisecondsSinceEpoch,
text: text,
completed: false,
));
_textController.clear();
});
}
void _toggleTodo(int id) {
setState(() {
final index = _todos.indexWhere((todo) => todo.id == id);
if (index != -1) {
_todos[index] = _todos[index].copyWith(completed: !_todos[index].completed);
}
});
}
void _deleteTodo(int id) {
setState(() {
_todos.removeWhere((todo) => todo.id == id);
});
}
void _clearCompleted() {
setState(() {
_todos.removeWhere((todo) => todo.completed);
});
}
void _updateTodo(int id, String newText) {
setState(() {
final index = _todos.indexWhere((todo) => todo.id == id);
if (index != -1) {
_todos[index] = _todos[index].copyWith(text: newText);
}
});
}
int get _completedCount => _todos.where((todo) => todo.completed).length;
int get _totalCount => _todos.length;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Todo App')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Add todo input
Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: const InputDecoration(
hintText: 'Add a new todo...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.add),
onPressed: _addTodo,
),
],
),
const SizedBox(height: 16),
// Stats
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Total: $_totalCount'),
Text('Completed: $_completedCount'),
if (_completedCount > 0)
TextButton(
onPressed: _clearCompleted,
child: const Text('Clear Completed'),
),
],
),
const SizedBox(height: 16),
// Todo list
Expanded(
child: ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
final todo = _todos[index];
return TodoItemWidget(
todo: todo,
onToggle: () => _toggleTodo(todo.id),
onDelete: () => _deleteTodo(todo.id),
onUpdate: (newText) => _updateTodo(todo.id, newText),
);
},
),
),
],
),
),
);
}
}
class TodoItem {
final int id;
final String text;
final bool completed;
const TodoItem({
required this.id,
required this.text,
required this.completed,
});
TodoItem copyWith({String? text, bool? completed}) {
return TodoItem(
id: id,
text: text ?? this.text,
completed: completed ?? this.completed,
);
}
}
class TodoItemWidget extends StatefulWidget {
final TodoItem todo;
final VoidCallback onToggle;
final VoidCallback onDelete;
final ValueChanged<String> onUpdate;
const TodoItemWidget({
super.key,
required this.todo,
required this.onToggle,
required this.onDelete,
required this.onUpdate,
});
@override
State<TodoItemWidget> createState() => _TodoItemWidgetState();
}
class _TodoItemWidgetState extends State<TodoItemWidget> {
bool _isEditing = false;
final TextEditingController _editController = TextEditingController();
@override
void initState() {
super.initState();
_editController.text = widget.todo.text;
}
void _startEditing() {
setState(() {
_isEditing = true;
});
}
void _saveEdit() {
if (_editController.text.trim().isNotEmpty) {
widget.onUpdate(_editController.text.trim());
}
setState(() {
_isEditing = false;
});
}
void _cancelEdit() {
setState(() {
_isEditing = false;
_editController.text = widget.todo.text;
});
}
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: Checkbox(
value: widget.todo.completed,
onChanged: (_) => widget.onToggle(),
),
title: _isEditing
? TextField(
controller: _editController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
autofocus: true,
onSubmitted: (_) => _saveEdit(),
)
: Text(
widget.todo.text,
style: TextStyle(
decoration: widget.todo.completed ? TextDecoration.lineThrough : null,
),
),
trailing: _isEditing
? Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(icon: const Icon(Icons.check), onPressed: _saveEdit),
IconButton(icon: const Icon(Icons.close), onPressed: _cancelEdit),
],
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(icon: const Icon(Icons.edit), onPressed: _startEditing),
IconButton(icon: const Icon(Icons.delete), onPressed: widget.onDelete),
],
),
),
);
}
}
Common Pitfalls
- Calling setState during build, dispose, or when the widget is unmounted
- Using setState for state that should be managed by parent widgets
- Not batching multiple state changes into a single setState call
- Forgetting to call setState when modifying state, causing UI inconsistencies
Provider State Management
Provider is a popular state management solution for Flutter that makes it easy to manage and access state across your widget tree using the provider pattern.
Basic Provider Setup
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Model class
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}
// Main app with Provider
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Provider Example',
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Provider Example')),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterDisplay(),
SizedBox(height: 20),
CounterButtons(),
],
),
),
);
}
}
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
final counter = Provider.of<CounterModel>(context);
return Text(
'${counter.count}',
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
);
}
}
class CounterButtons extends StatelessWidget {
const CounterButtons({super.key});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => Provider.of<CounterModel>(context, listen: false).decrement(),
child: const Text('-'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () => Provider.of<CounterModel>(context, listen: false).reset(),
child: const Text('Reset'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () => Provider.of<CounterModel>(context, listen: false).increment(),
child: const Text('+'),
),
],
);
}
}
Advanced Provider Patterns
// User model and service
class User {
final String id;
final String name;
final String email;
const User({required this.id, required this.name, required this.email});
User copyWith({String? name, String? email}) {
return User(
id: id,
name: name ?? this.name,
email: email ?? this.email,
);
}
}
class UserService extends ChangeNotifier {
User? _currentUser;
bool _isLoading = false;
User? get currentUser => _currentUser;
bool get isLoading => _isLoading;
bool get isLoggedIn => _currentUser != null;
Future<void> login(String email, String password) async {
_isLoading = true;
notifyListeners();
try {
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
_currentUser = User(
id: '1',
name: 'John Doe',
email: email,
);
} catch (e) {
rethrow;
} finally {
_isLoading = false;
notifyListeners();
}
}
void logout() {
_currentUser = null;
notifyListeners();
}
void updateProfile(String name, String email) {
if (_currentUser != null) {
_currentUser = _currentUser!.copyWith(name: name, email: email);
notifyListeners();
}
}
}
// Multi-provider setup
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserService()),
ChangeNotifierProvider(create: (_) => ThemeService()),
Provider(create: (_) => ApiService()),
],
child: const MyApp(),
),
);
}
class ThemeService extends ChangeNotifier {
bool _isDarkMode = false;
bool get isDarkMode => _isDarkMode;
ThemeData get themeData => _isDarkMode ? _darkTheme : _lightTheme;
static final ThemeData _lightTheme = ThemeData.light();
static final ThemeData _darkTheme = ThemeData.dark();
void toggleTheme() {
_isDarkMode = !_isDarkMode;
notifyListeners();
}
}
class ApiService {
// API service methods...
}
// Consumer and Selector usage
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: Consumer<UserService>(
builder: (context, userService, child) {
if (userService.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (!userService.isLoggedIn) {
return const Center(child: Text('Please log in'));
}
final user = userService.currentUser!;
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
CircleAvatar(
child: Text(user.name[0]),
radius: 40,
),
const SizedBox(height: 16),
Text(user.name, style: const TextStyle(fontSize: 24)),
Text(user.email),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _showEditProfileDialog(context, userService),
child: const Text('Edit Profile'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => userService.logout(),
child: const Text('Logout'),
),
],
),
);
},
),
);
}
void _showEditProfileDialog(BuildContext context, UserService userService) {
final nameController = TextEditingController(text: userService.currentUser!.name);
final emailController = TextEditingController(text: userService.currentUser!.email);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Edit Profile'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: nameController, decoration: const InputDecoration(labelText: 'Name')),
TextField(controller: emailController, decoration: const InputDecoration(labelText: 'Email')),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
userService.updateProfile(nameController.text, emailController.text);
Navigator.pop(context);
},
child: const Text('Save'),
),
],
),
);
}
}
// Selector for optimized rebuilds
class UserNameDisplay extends StatelessWidget {
const UserNameDisplay({super.key});
@override
Widget build(BuildContext context) {
return Selector<UserService, String?>(
selector: (context, userService) => userService.currentUser?.name,
builder: (context, userName, child) {
return Text(userName ?? 'Guest', style: const TextStyle(fontSize: 18));
},
);
}
}
Common Pitfalls
- Forgetting to call notifyListeners() when state changes
- Using Provider.of without listen: false when only needing access to methods
- Not using Selector for expensive widgets that only depend on specific parts of state
- Creating providers in the wrong place in the widget tree
BLoC Pattern in Flutter
The BLoC (Business Logic Component) pattern helps separate business logic from presentation logic, making your code more testable, reusable, and maintainable.
Basic BLoC Implementation
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// Events
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class ResetEvent extends CounterEvent {}
// States
abstract class CounterState {
final int count;
const CounterState(this.count);
}
class CounterInitial extends CounterState {
CounterInitial() : super(0);
}
class CounterUpdated extends CounterState {
CounterUpdated(int count) : super(count);
}
// BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitial()) {
on<IncrementEvent>((event, emit) {
emit(CounterUpdated(state.count + 1));
});
on<DecrementEvent>((event, emit) {
emit(CounterUpdated(state.count - 1));
});
on<ResetEvent>((event, emit) {
emit(CounterUpdated(0));
});
}
}
// BLoC Provider setup
void main() {
runApp(
BlocProvider(
create: (context) => CounterBloc(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BLoC Example',
theme: ThemeData(primarySwatch: Colors.blue),
home: const CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('BLoC Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'${state.count}',
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
);
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(DecrementEvent()),
child: const Text('-'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(ResetEvent()),
child: const Text('Reset'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
child: const Text('+'),
),
],
),
],
),
),
);
}
}
Advanced BLoC Patterns
// Todo BLoC example
// Events
abstract class TodoEvent {}
class LoadTodosEvent extends TodoEvent {}
class AddTodoEvent extends TodoEvent {
final String text;
AddTodoEvent(this.text);
}
class ToggleTodoEvent extends TodoEvent {
final String id;
ToggleTodoEvent(this.id);
}
class DeleteTodoEvent extends TodoEvent {
final String id;
DeleteTodoEvent(this.id);
}
// States
abstract class TodoState {}
class TodoLoading extends TodoState {}
class TodoLoaded extends TodoState {
final List<Todo> todos;
TodoLoaded(this.todos);
}
class TodoError extends TodoState {
final String message;
TodoError(this.message);
}
// Model
class Todo {
final String id;
final String text;
final bool completed;
final DateTime createdAt;
Todo({
required this.id,
required this.text,
this.completed = false,
required this.createdAt,
});
Todo copyWith({String? text, bool? completed}) {
return Todo(
id: id,
text: text ?? this.text,
completed: completed ?? this.completed,
createdAt: createdAt,
);
}
}
// BLoC
class TodoBloc extends Bloc<TodoEvent, TodoState> {
final List<Todo> _todos = [];
TodoBloc() : super(TodoLoading()) {
on<LoadTodosEvent>((event, emit) async {
emit(TodoLoading());
await Future.delayed(const Duration(seconds: 1)); // Simulate loading
emit(TodoLoaded(List.from(_todos)));
});
on<AddTodoEvent>((event, emit) {
final todo = Todo(
id: DateTime.now().millisecondsSinceEpoch.toString(),
text: event.text,
createdAt: DateTime.now(),
);
_todos.add(todo);
emit(TodoLoaded(List.from(_todos)));
});
on<ToggleTodoEvent>((event, emit) {
final index = _todos.indexWhere((todo) => todo.id == event.id);
if (index != -1) {
_todos[index] = _todos[index].copyWith(completed: !_todos[index].completed);
emit(TodoLoaded(List.from(_todos)));
}
});
on<DeleteTodoEvent>((event, emit) {
_todos.removeWhere((todo) => todo.id == event.id);
emit(TodoLoaded(List.from(_todos)));
});
}
}
// Multi-BLoC setup
void main() {
runApp(
MultiBlocProvider(
providers: [
BlocProvider(create: (context) => TodoBloc()),
BlocProvider(create: (context) => ThemeBloc()),
],
child: const MyApp(),
),
);
}
// Theme BLoC
abstract class ThemeEvent {}
class ToggleThemeEvent extends ThemeEvent {}
class ThemeState {
final bool isDarkMode;
ThemeState(this.isDarkMode);
}
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
ThemeBloc() : super(ThemeState(false)) {
on<ToggleThemeEvent>((event, emit) {
emit(ThemeState(!state.isDarkMode));
});
}
}
// Todo App with BLoC
class TodoApp extends StatelessWidget {
const TodoApp({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, themeState) {
return MaterialApp(
title: 'Todo BLoC',
theme: themeState.isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: const TodoListPage(),
);
},
);
}
}
class TodoListPage extends StatelessWidget {
const TodoListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todo BLoC'),
actions: [
IconButton(
icon: const Icon(Icons.brightness_6),
onPressed: () => context.read<ThemeBloc>().add(ToggleThemeEvent()),
),
],
),
body: BlocConsumer<TodoBloc, TodoState>(
listener: (context, state) {
if (state is TodoError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
}
},
builder: (context, state) {
if (state is TodoLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state is TodoLoaded) {
final todos = state.todos;
return Column(
children: [
TodoInput(),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) => TodoItem(todo: todos[index]),
),
),
],
);
}
return const Center(child: Text('Something went wrong'));
},
),
);
}
}
class TodoInput extends StatefulWidget {
@override
State<TodoInput> createState() => _TodoInputState();
}
class _TodoInputState extends State<TodoInput> {
final TextEditingController _controller = TextEditingController();
void _addTodo() {
final text = _controller.text.trim();
if (text.isNotEmpty) {
context.read<TodoBloc>().add(AddTodoEvent(text));
_controller.clear();
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Add a new todo...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.add),
onPressed: _addTodo,
),
],
),
);
}
}
class TodoItem extends StatelessWidget {
final Todo todo;
const TodoItem({super.key, required this.todo});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
value: todo.completed,
onChanged: (_) => context.read<TodoBloc>().add(ToggleTodoEvent(todo.id)),
),
title: Text(
todo.text,
style: TextStyle(
decoration: todo.completed ? TextDecoration.lineThrough : null,
),
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => context.read<TodoBloc>().add(DeleteTodoEvent(todo.id)),
),
);
}
}
Common Pitfalls
- Not handling all possible states in BlocBuilder
- Adding events to BLoC after it's closed
- Creating BLoCs in the wrong place in the widget tree
- Not using BlocListener for side effects that shouldn't rebuild UI
Advanced Flutter Concepts
Advanced Flutter concepts include performance optimization, platform integration, custom painting, animations, and other powerful features for building production-ready apps.
Performance Optimization
import 'package:flutter/material.dart';
// Efficient list rendering
class OptimizedListView extends StatelessWidget {
final List<String> items;
const OptimizedListView({super.key, required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListItem(
item: items[index],
index: index,
),
);
}
}
class ListItem extends StatelessWidget {
final String item;
final int index;
const ListItem({super.key, required this.item, required this.index});
@override
Widget build(BuildContext context) {
return ListTile(
leading: CircleAvatar(child: Text('$index')),
title: Text(item),
subtitle: Text('Item description $index'),
);
}
}
// Const constructors for performance
class PerformanceExample extends StatelessWidget {
const PerformanceExample({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: SafeArea(
child: Column(
children: [
SizedBox(height: 16),
Text('Performance Tips', style: TextStyle(fontSize: 24)),
SizedBox(height: 16),
_OptimizedWidget(),
SizedBox(height: 16),
_AnotherOptimizedWidget(),
],
),
),
);
}
}
class _OptimizedWidget extends StatelessWidget {
const _OptimizedWidget();
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.all(16),
child: Text('This widget uses const constructors'),
);
}
}
class _AnotherOptimizedWidget extends StatelessWidget {
const _AnotherOptimizedWidget();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: const Text('Another optimized widget'),
);
}
}
// Using keys for state preservation
class KeyExample extends StatefulWidget {
const KeyExample({super.key});
@override
State<KeyExample> createState() => _KeyExampleState();
}
class _KeyExampleState extends State<KeyExample> {
final List<Widget> items = [
_StatefulItem(key: UniqueKey(), color: Colors.red),
_StatefulItem(key: UniqueKey(), color: Colors.green),
_StatefulItem(key: UniqueKey(), color: Colors.blue),
];
void _shuffleItems() {
setState(() {
items.shuffle();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Keys Example')),
body: Column(
children: [
ElevatedButton(
onPressed: _shuffleItems,
child: const Text('Shuffle Items'),
),
Expanded(
child: ListView(children: items),
),
],
),
);
}
}
class _StatefulItem extends StatefulWidget {
final Color color;
const _StatefulItem({super.key, required this.color});
@override
State<_StatefulItem> createState() => _StatefulItemState();
}
class _StatefulItemState extends State<_StatefulItem> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
color: widget.color.withOpacity(0.3),
child: Row(
children: [
Text('Counter: $_counter'),
const Spacer(),
ElevatedButton(
onPressed: () => setState(() => _counter++),
child: const Text('Increment'),
),
],
),
);
}
}
Platform Integration and Native Features
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:image_picker/image_picker.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:camera/camera.dart';
class PlatformIntegrationExample extends StatefulWidget {
const PlatformIntegrationExample({super.key});
@override
State<PlatformIntegrationExample> createState() => _PlatformIntegrationExampleState();
}
class _PlatformIntegrationExampleState extends State<PlatformIntegrationExample> {
final ImagePicker _picker = ImagePicker();
String _preferenceValue = '';
final TextEditingController _preferenceController = TextEditingController();
// URL Launcher
Future<void> _launchURL(String url) async {
final Uri uri = Uri.parse(url);
if (!await launchUrl(uri)) {
throw Exception('Could not launch $url');
}
}
// Image Picker
Future<void> _pickImage() async {
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
// Handle the picked image
print('Picked image: ${image.path}');
}
}
// Shared Preferences
Future<void> _savePreference() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('my_key', _preferenceController.text);
_loadPreference();
}
Future<void> _loadPreference() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_preferenceValue = prefs.getString('my_key') ?? 'Not set';
});
}
@override
void initState() {
super.initState();
_loadPreference();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Platform Integration')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// URL Launcher
const Text('URL Launcher', style: TextStyle(fontWeight: FontWeight.bold)),
Wrap(
spacing: 8,
children: [
ElevatedButton(
onPressed: () => _launchURL('https://flutter.dev'),
child: const Text('Open Flutter Website'),
),
ElevatedButton(
onPressed: () => _launchURL('tel:+1234567890'),
child: const Text('Make Phone Call'),
),
ElevatedButton(
onPressed: () => _launchURL('mailto:support@example.com'),
child: const Text('Send Email'),
),
],
),
const SizedBox(height: 20),
// Image Picker
const Text('Image Picker', style: TextStyle(fontWeight: FontWeight.bold)),
ElevatedButton(
onPressed: _pickImage,
child: const Text('Pick Image from Gallery'),
),
const SizedBox(height: 20),
// Shared Preferences
const Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold)),
TextField(
controller: _preferenceController,
decoration: const InputDecoration(
labelText: 'Enter value to save',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 8),
Row(
children: [
ElevatedButton(
onPressed: _savePreference,
child: const Text('Save Preference'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _loadPreference,
child: const Text('Load Preference'),
),
],
),
Text('Stored value: $_preferenceValue'),
],
),
),
);
}
}
// Custom platform channels example (simplified)
class NativeIntegration extends StatefulWidget {
const NativeIntegration({super.key});
@override
State<NativeIntegration> createState() => _NativeIntegrationState();
}
class _NativeIntegrationState extends State<NativeIntegration> {
// This would typically use MethodChannel for platform-specific functionality
// For demonstration, we'll simulate the behavior
Future<void> _showNativeDialog() async {
// In real implementation, this would call platform-specific code
// via MethodChannel.invokeMethod('showDialog', {'message': 'Hello from Flutter!'});
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Native Dialog Simulation'),
content: const Text('This simulates a native platform dialog.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Native Integration')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _showNativeDialog,
child: const Text('Show Native Dialog'),
),
],
),
),
);
}
}
Advanced Animations and Custom Painters
import 'package:flutter/material.dart';
class AdvancedAnimations extends StatefulWidget {
const AdvancedAnimations({super.key});
@override
State<AdvancedAnimations> createState() => _AdvancedAnimationsState();
}
class _AdvancedAnimationsState extends State<AdvancedAnimations>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<Color?> _colorAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 1.5,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(_controller);
_slideAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.5, 0),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Advanced Animations')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: _slideAnimation.value,
child: Transform.scale(
scale: _scaleAnimation.value,
child: Container(
width: 100,
height: 100,
color: _colorAnimation.value,
child: child,
),
),
);
},
child: const Icon(Icons.star, color: Colors.white, size: 40),
),
const SizedBox(height: 40),
SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _controller,
child: const Text('Animated Text'),
),
),
const SizedBox(height: 40),
CustomPaint(
size: const Size(200, 200),
painter: MyCustomPainter(animation: _controller),
),
],
),
),
);
}
}
class MyCustomPainter extends CustomPainter {
final Animation<double> animation;
MyCustomPainter({required this.animation}) : super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue.withOpacity(0.5)
..style = PaintingStyle.stroke
..strokeWidth = 3;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 3 * animation.value;
canvas.drawCircle(center, radius, paint);
// Draw rotating line
final angle = 2 * 3.14159 * animation.value;
final endPoint = Offset(
center.dx + radius * cos(angle),
center.dy + radius * sin(angle),
);
canvas.drawLine(center, endPoint, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// Hero animations
class HeroAnimationExample extends StatelessWidget {
const HeroAnimationExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hero Animations')),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 6,
itemBuilder: (context, index) => GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(index: index),
),
),
child: Hero(
tag: 'image$index',
child: Container(
color: Colors.primaries[index],
child: Center(
child: Text(
'Item $index',
style: const TextStyle(color: Colors.white, fontSize: 20),
),
),
),
),
),
),
);
}
}
class DetailPage extends StatelessWidget {
final int index;
const DetailPage({super.key, required this.index});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Detail $index')),
body: Center(
child: Hero(
tag: 'image$index',
child: Container(
width: 300,
height: 300,
color: Colors.primaries[index],
child: Center(
child: Text(
'Detail $index',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
),
);
}
}
Common Pitfalls
- Not disposing animation controllers and other resources
- Using expensive operations in build methods
- Not testing platform-specific code on actual devices
- Over-optimizing prematurely without performance profiling