Skip to main content

Flutter

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

  1. 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.

  2. macOS
    Download the Flutter SDK and extract it. Alternatively, use Homebrew:

    brew install --cask flutter
  3. 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

  1. Create a new Flutter project:

    flutter create my_first_app
  2. Navigate to the project directory:

    cd my_first_app
  3. 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, and const

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 a double, even for integer operands
  • Use ~/ for integer division that truncates the result
  • Division by zero returns double.infinity or double.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 and hashCode for 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 returns double, 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 runes for 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 - use StringBuffer)
  • 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() returns Iterable, not List
  • 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 reduce on empty lists causes StateError
  • 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 null instead of throwing error
  • Using mutable objects as keys can cause unpredictable behavior
  • Forgetting that map iteration order is not guaranteed (use LinkedHashMap for 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 LinkedHashSet for order)
  • Custom objects in sets need proper hashCode and == 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 toString overrides 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 super in constructor when extending classes
  • Not marking fields as final when they shouldn't change
  • Overusing inheritance when composition would be better
  • Not implementing hashCode when 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 super in overridden methods when needed
  • Using inheritance for "is-a" relationships instead of "has-a" (use composition)
  • Not making classes abstract when 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 var when the type isn't clear from the initializer
  • Accessing late variables before they're initialized
  • Overusing global variables instead of proper state management
  • Not using final for 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.builder for 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 reduce on 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 break when continue is more appropriate
  • Forgetting that break only 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 await on 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