Aller au contenu

Flutter Cheatsheet

Overview

Flutter is Google's 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.

Installation

Prerequisites

# Check system requirements
flutter doctor

# Install Git
git --version

Install Flutter SDK

# macOS
# Download Flutter SDK from flutter.dev
# Extract to desired location
export PATH="$PATH:`pwd`/flutter/bin"

# Add to shell profile
echo 'export PATH="$PATH:/path/to/flutter/bin"' >> ~/.zshrc
source ~/.zshrc

# Linux
sudo snap install flutter --classic

# Windows
# Download Flutter SDK
# Extract and add to PATH

IDE Setup

# VS Code extensions
code --install-extension Dart-Code.dart-code
code --install-extension Dart-Code.flutter

# Android Studio plugins
# Install Flutter and Dart plugins

Verification

flutter doctor
flutter --version
dart --version

Project Creation

Create New Project

# Create Flutter app
flutter create my_app
cd my_app

# Create with specific organization
flutter create --org com.exemple my_app

# Create with different platforms
flutter create --platforms android,ios,web my_app

# Create with template
flutter create --template=plugin my_plugin
flutter create --template=package my_package

Project Structure

my_app/
├── android/
├── ios/
├── web/
├── lib/
│   ├── main.dart
│   ├── models/
│   ├── screens/
│   ├── widgets/
│   └── services/
├── test/
├── assets/
│   ├── images/
│   └── fonts/
├── pubspec.yaml
└── README.md

Basic Dart syntaxe

Variables and Types

// Variable declarations
var name = 'Flutter';
String title = 'My App';
int count = 0;
double price = 99.99;
bool isActive = true;
List<String> items = ['item1', 'item2'];
Map<String, dynamic> user = \\\\{'name': 'John', 'age': 30\\\\};

// Nullable types
String? nullableName;
int? nullableCount;

// Late variables
late String Description;

// Constants
const String appName = 'My App';
final DateTime now = DateTime.now();

Functions

// Basic function
String greet(String name) \\\\{
  return 'Hello, $name!';
\\\\}

// Arrow function
String greetShort(String name) => 'Hello, $name!';

// optional paramètres
String greetoptional(String name, [String? title]) \\\\{
  return title != null ? 'Hello, $title $name!' : 'Hello, $name!';
\\\\}

// Named paramètres
String greetNamed(\\\\{required String name, String title = 'Mr.'\\\\}) \\\\{
  return 'Hello, $title $name!';
\\\\}

// Async function
Future<String> fetchData() async \\\\{
  await Future.delayed(Duration(seconds: 2));
  return 'Data loaded';
\\\\}

Classes

class User \\\\{
  final String name;
  final int age;
  String? email;

  // Constructor
  User(\\\\{required this.name, required this.age, this.email\\\\});

  // Named constructor
  User.guest() : name = 'Guest', age = 0;

  // Method
  String getDisplayName() \\\\{
    return email != null ? '$name ($email)' : name;
  \\\\}

  // Getter
  bool get isAdult => age >= 18;

  // Setter
  set userEmail(String email) => this.email = email;

  @override
  String toString() => 'User(name: $name, age: $age)';
\\\\}

// utilisation
final user = User(name: 'John', age: 25);
final guest = User.guest();

Basic Widgets

Stateless Widget

import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget \\\\{
  final String title;
  final VoidCallback? onTap;

  const MyWidget(\\\\{
    clé? clé,
    required this.title,
    this.onTap,
  \\\\}) : super(clé: clé);

  @override
  Widget build(BuildContext context) \\\\{
    return Container(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        children: [
          Text(
            title,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          if (onTap != null)
            ElevatedButton(
              onPressed: onTap,
              child: const Text('Tap Me'),
            ),
        ],
      ),
    );
  \\\\}
\\\\}

Stateful Widget

class CounterWidget extends StatefulWidget \\\\{
  final int initialValue;

  const CounterWidget(\\\\{clé? clé, this.initialValue = 0\\\\}) : super(clé: clé);

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
\\\\}

class _CounterWidgetState extends State<CounterWidget> \\\\{
  late int _counter;

  @override
  void initState() \\\\{
    super.initState();
    _counter = widget.initialValue;
  \\\\}

  void _increment() \\\\{
    setState(() \\\\{
      _counter++;
    \\\\});
  \\\\}

  void _decrement() \\\\{
    setState(() \\\\{
      _counter--;
    \\\\});
  \\\\}

  @override
  Widget build(BuildContext context) \\\\{
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          'Counter: $_counter',
          style: Theme.of(context).textTheme.headlineMedium,
        ),
        const SizedBox(height: 20),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton(
              onPressed: _decrement,
              child: const Text('-'),
            ),
            ElevatedButton(
              onPressed: _increment,
              child: const Text('+'),
            ),
          ],
        ),
      ],
    );
  \\\\}
\\\\}

Layout Widgets

Container and Padding

Container(
  width: 200,
  height: 100,
  padding: const EdgeInsets.all(16),
  margin: const EdgeInsets.symmetric(vertical: 8),
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(8),
    boxShadow: [
      BoxShadow(
        color: Colors.grey.withOpacity(0.5),
        spreadRadius: 2,
        blurRadius: 5,
        offset: const Offset(0, 3),
      ),
    ],
  ),
  child: const Text('Hello Flutter'),
)

Row and Column

// Row
Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Icon(Icons.star),
    Text('Rating'),
    Text('4.5'),
  ],
)

// Column
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('Title'),
    Text('Subtitle'),
    ElevatedButton(
      onPressed: () \\\\{\\\\},
      child: Text('Action'),
    ),
  ],
)

Stack and Positioned

Stack(
  children: [
    Container(
      width: 200,
      height: 200,
      color: Colors.blue,
    ),
    Positioned(
      top: 20,
      right: 20,
      child: Icon(Icons.favorite, color: Colors.red),
    ),
    Positioned(
      bottom: 20,
      left: 20,
      child: Text('Bottom Left'),
    ),
  ],
)

Flexible and Expanded

Row(
  children: [
    Flexible(
      flex: 1,
      child: Container(color: Colors.red, height: 100),
    ),
    Expanded(
      flex: 2,
      child: Container(color: Colors.green, height: 100),
    ),
    Flexible(
      flex: 1,
      child: Container(color: Colors.blue, height: 100),
    ),
  ],
)

Common Widgets

Text and RichText

// Basic text
Text(
  'Hello Flutter',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
)

// Rich text
RichText(
  text: TextSpan(
    style: DefaultTextStyle.of(context).style,
    children: [
      TextSpan(text: 'Hello '),
      TextSpan(
        text: 'Flutter',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Colors.blue,
        ),
      ),
    ],
  ),
)

Buttons

// Elevated Button
ElevatedButton(
  onPressed: () \\\\{
    print('Button pressed');
  \\\\},
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.blue,
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
  ),
  child: Text('Elevated Button'),
)

// Text Button
TextButton(
  onPressed: () \\\\{\\\\},
  child: Text('Text Button'),
)

// Outlined Button
OutlinedButton(
  onPressed: () \\\\{\\\\},
  child: Text('Outlined Button'),
)

// Icon Button
IconButton(
  onPressed: () \\\\{\\\\},
  icon: Icon(Icons.favorite),
)

// Floating Action Button
FloatingActionButton(
  onPressed: () \\\\{\\\\},
  child: Icon(Icons.add),
)

Input Widgets

// TextField
TextField(
  decoration: InputDecoration(
    labelText: 'Enter your name',
    hintText: 'John Doe',
    border: OutlineInputBorder(),
    prefixIcon: Icon(Icons.person),
  ),
  onChanged: (value) \\\\{
    print('Input: $value');
  \\\\},
)

// TextFormField with validation
TextFormField(
  validator: (value) \\\\{
| if (value == null |  | value.isEmpty) \\\\{ |
      return 'Please enter some text';
    \\\\}
    return null;
  \\\\},
  decoration: InputDecoration(
    labelText: 'Email',
    border: OutlineInputBorder(),
  ),
)

// Checkbox
Checkbox(
  value: isChecked,
  onChanged: (bool? value) \\\\{
    setState(() \\\\{
      isChecked = value ?? false;
    \\\\});
  \\\\},
)

// Switch
Switch(
  value: isSwitched,
  onChanged: (bool value) \\\\{
    setState(() \\\\{
      isSwitched = value;
    \\\\});
  \\\\},
)

// Slider
Slider(
  value: sliderValue,
  min: 0,
  max: 100,
  divisions: 10,
  label: sliderValue.round().toString(),
  onChanged: (double value) \\\\{
    setState(() \\\\{
      sliderValue = value;
    \\\\});
  \\\\},
)

Lists and Grids

ListView

// Basic ListView
ListView(
  children: [
    ListTile(
      leading: Icon(Icons.person),
      title: Text('John Doe'),
      subtitle: Text('john@exemple.com'),
      trailing: Icon(Icons.arrow_forward),
      onTap: () \\\\{\\\\},
    ),
    ListTile(
      leading: Icon(Icons.person),
      title: Text('Jane Smith'),
      subtitle: Text('jane@exemple.com'),
      trailing: Icon(Icons.arrow_forward),
      onTap: () \\\\{\\\\},
    ),
  ],
)

// ListView.builder
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) \\\\{
    return ListTile(
      title: Text(items[index].title),
      subtitle: Text(items[index].Description),
      onTap: () \\\\{
        // Handle tap
      \\\\},
    );
  \\\\},
)

// ListView.separated
ListView.separated(
  itemCount: items.length,
  separatorBuilder: (context, index) => Divider(),
  itemBuilder: (context, index) \\\\{
    return ListTile(
      title: Text(items[index]),
    );
  \\\\},
)

GridView

// GridView.count
GridView.count(
  crossAxisCount: 2,
  crossAxisSpacing: 10,
  mainAxisSpacing: 10,
  children: List.generate(20, (index) \\\\{
    return Container(
      decoration: BoxDecoration(
        color: Colors.blue[100],
        borderRadius: BorderRadius.circular(8),
      ),
      child: Center(
        child: Text('Item $index'),
      ),
    );
  \\\\}),
)

// GridView.builder
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 10,
    mainAxisSpacing: 10,
  ),
  itemCount: items.length,
  itemBuilder: (context, index) \\\\{
    return Card(
      child: Column(
        children: [
          Image.network(items[index].imageUrl),
          Text(items[index].title),
        ],
      ),
    );
  \\\\},
)

Basic Navigation

// Navigate to new screen
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SecondScreen()),
);

// Navigate with data
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailScreen(item: selectedItem),
  ),
);

// Navigate and replace
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => HomeScreen()),
);

// Pop back
Navigator.pop(context);

// Pop with result
Navigator.pop(context, 'result data');

Named Routes

// main.dart
MaterialApp(
  initialRoute: '/',
  routes: \\\\{
    '/': (context) => HomeScreen(),
    '/second': (context) => SecondScreen(),
    '/third': (context) => ThirdScreen(),
  \\\\},
)

// Navigate using named routes
Navigator.pushNamed(context, '/second');

// Navigate with arguments
Navigator.pushNamed(
  context,
  '/detail',
  arguments: \\\\{'id': 123, 'title': 'Item Title'\\\\},
);

// Extract arguments
class DetailScreen extends StatelessWidget \\\\{
  @override
  Widget build(BuildContext context) \\\\{
    final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;

    return Scaffold(
      appBar: AppBar(title: Text(args['title'])),
      body: Text('ID: $\\\\{args['id']\\\\}'),
    );
  \\\\}
\\\\}

Advanced Navigation

// Navigate and clear stack
Navigator.pushNamedAndRemoveUntil(
  context,
  '/home',
  (route) => false,
);

// Navigate with custom transition
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => SecondScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) \\\\{
      return SlideTransition(
        position: animation.drive(
          Tween(begin: Offset(1.0, 0.0), end: Offset.zero),
        ),
        child: child,
      );
    \\\\},
  ),
);

State Management

setState

class CounterScreen extends StatefulWidget \\\\{
  @override
  _CounterScreenState createState() => _CounterScreenState();
\\\\}

class _CounterScreenState extends State<CounterScreen> \\\\{
  int _counter = 0;

  void _incrementCounter() \\\\{
    setState(() \\\\{
      _counter++;
    \\\\});
  \\\\}

  @override
  Widget build(BuildContext context) \\\\{
    return Scaffold(
      body: Center(
        child: Text('Counter: $_counter'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  \\\\}
\\\\}

Provider

# pubspec.yaml
dependencies:
  provider: ^6.0.5
// Model
class Counter extends ChangeNotifier \\\\{
  int _count = 0;

  int get count => _count;

  void increment() \\\\{
    _count++;
    notifyListeners();
  \\\\}

  void decrement() \\\\{
    _count--;
    notifyListeners();
  \\\\}
\\\\}

// main.dart
void main() \\\\{
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
\\\\}

// Consumer widget
class CounterScreen extends StatelessWidget \\\\{
  @override
  Widget build(BuildContext context) \\\\{
    return Scaffold(
      body: Center(
        child: Consumer<Counter>(
          builder: (context, counter, child) \\\\{
            return Text('Count: $\\\\{counter.count\\\\}');
          \\\\},
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () \\\\{
          Provider.of<Counter>(context, listen: false).increment();
        \\\\},
        child: Icon(Icons.add),
      ),
    );
  \\\\}
\\\\}

Riverpod

# pubspec.yaml
dependencies:
  flutter_riverpod: ^2.4.9
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Provider
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) \\\\{
  return CounterNotifier();
\\\\});

class CounterNotifier extends StateNotifier<int> \\\\{
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
\\\\}

// main.dart
void main() \\\\{
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
\\\\}

// Consumer widget
class CounterScreen extends ConsumerWidget \\\\{
  @override
  Widget build(BuildContext context, WidgetRef ref) \\\\{
    final count = ref.watch(counterProvider);

    return Scaffold(
      body: Center(
        child: Text('Count: $count'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () \\\\{
          ref.read(counterProvider.notifier).increment();
        \\\\},
        child: Icon(Icons.add),
      ),
    );
  \\\\}
\\\\}

HTTP Requests

Basic HTTP

# pubspec.yaml
dependencies:
  http: ^1.1.0
import 'package:http/http.dart' as http;
import 'dart:convert';

class Apiservice \\\\{
  static const String baseUrl = 'https://jsonplaceholder.typicode.com';

  static Future<List<Post>> fetchPosts() async \\\\{
    final response = await http.get(Uri.parse('$baseUrl/posts'));

    if (response.statusCode == 200) \\\\{
      final List<dynamic> jsonData = json.decode(response.body);
      return jsonData.map((json) => Post.fromJson(json)).toList();
    \\\\} else \\\\{
      throw Exception('Failed to load posts');
    \\\\}
  \\\\}

  static Future<Post> createPost(Post post) async \\\\{
    final response = await http.post(
      Uri.parse('$baseUrl/posts'),
      headers: \\\\{'Content-Type': 'application/json'\\\\},
      body: json.encode(post.toJson()),
    );

    if (response.statusCode == 201) \\\\{
      return Post.fromJson(json.decode(response.body));
    \\\\} else \\\\{
      throw Exception('Failed to create post');
    \\\\}
  \\\\}

  static Future<void> deletePost(int id) async \\\\{
    final response = await http.delete(Uri.parse('$baseUrl/posts/$id'));

    if (response.statusCode != 200) \\\\{
      throw Exception('Failed to delete post');
    \\\\}
  \\\\}
\\\\}

// Model
class Post \\\\{
  final int id;
  final String title;
  final String body;

  Post(\\\\{required this.id, required this.title, required this.body\\\\});

  factory Post.fromJson(Map<String, dynamic> json) \\\\{
    return Post(
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  \\\\}

  Map<String, dynamic> toJson() \\\\{
    return \\\\{
      'id': id,
      'title': title,
      'body': body,
    \\\\};
  \\\\}
\\\\}

FutureBuilder

class PostsList extends StatelessWidget \\\\{
  @override
  Widget build(BuildContext context) \\\\{
    return Scaffold(
      appBar: AppBar(title: Text('Posts')),
      body: FutureBuilder<List<Post>>(
        future: Apiservice.fetchPosts(),
        builder: (context, snapshot) \\\\{
          if (snapshot.connexionState == connexionState.waiting) \\\\{
            return Center(child: CircularProgressIndicator());
          \\\\} else if (snapshot.hasError) \\\\{
            return Center(child: Text('Error: $\\\\{snapshot.error\\\\}'));
| \\\\} else if (!snapshot.hasData |  | snapshot.data!.isEmpty) \\\\{ |
            return Center(child: Text('No posts found'));
          \\\\} else \\\\{
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) \\\\{
                final post = snapshot.data![index];
                return ListTile(
                  title: Text(post.title),
                  subtitle: Text(post.body),
                );
              \\\\},
            );
          \\\\}
        \\\\},
      ),
    );
  \\\\}
\\\\}

Local Storage

SharedPréférences

# pubspec.yaml
dependencies:
  shared_préférences: ^2.2.2
import 'package:shared_préférences/shared_préférences.dart';

class Préférencesservice \\\\{
  static Future<void> saveString(String clé, String value) async \\\\{
    final prefs = await SharedPréférences.getInstance();
    await prefs.setString(clé, value);
  \\\\}

  static Future<String?> getString(String clé) async \\\\{
    final prefs = await SharedPréférences.getInstance();
    return prefs.getString(clé);
  \\\\}

  static Future<void> saveInt(String clé, int value) async \\\\{
    final prefs = await SharedPréférences.getInstance();
    await prefs.setInt(clé, value);
  \\\\}

  static Future<int?> getInt(String clé) async \\\\{
    final prefs = await SharedPréférences.getInstance();
    return prefs.getInt(clé);
  \\\\}

  static Future<void> saveBool(String clé, bool value) async \\\\{
    final prefs = await SharedPréférences.getInstance();
    await prefs.setBool(clé, value);
  \\\\}

  static Future<bool?> getBool(String clé) async \\\\{
    final prefs = await SharedPréférences.getInstance();
    return prefs.getBool(clé);
  \\\\}

  static Future<void> remove(String clé) async \\\\{
    final prefs = await SharedPréférences.getInstance();
    await prefs.remove(clé);
  \\\\}

  static Future<void> clear() async \\\\{
    final prefs = await SharedPréférences.getInstance();
    await prefs.clear();
  \\\\}
\\\\}

SQLite Database

# pubspec.yaml
dependencies:
  sqflite: ^2.3.0
  path: ^1.8.3
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper \\\\{
  static Database? _database;
  static const String tableName = 'users';

  static Future<Database> get database async \\\\{
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  \\\\}

  static Future<Database> _initDatabase() async \\\\{
    String path = join(await getDatabasesPath(), 'app_database.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _createDatabase,
    );
  \\\\}

  static Future<void> _createDatabase(Database db, int version) async \\\\{
    await db.execute('''
      CREATE TABLE $tableName (
        id INTEGER PRIMARY clé AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT NOT NULL,
        created_at TEXT NOT NULL
      )
    ''');
  \\\\}

  static Future<int> insertUser(User user) async \\\\{
    final db = await database;
    return await db.insert(tableName, user.toMap());
  \\\\}

  static Future<List<User>> getAllUsers() async \\\\{
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query(tableName);
    return List.generate(maps.length, (i) => User.fromMap(maps[i]));
  \\\\}

  static Future<User?> getUserById(int id) async \\\\{
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query(
      tableName,
      where: 'id = ?',
      whereArgs: [id],
    );
    if (maps.isNotEmpty) \\\\{
      return User.fromMap(maps.first);
    \\\\}
    return null;
  \\\\}

  static Future<int> updateUser(User user) async \\\\{
    final db = await database;
    return await db.update(
      tableName,
      user.toMap(),
      where: 'id = ?',
      whereArgs: [user.id],
    );
  \\\\}

  static Future<int> deleteUser(int id) async \\\\{
    final db = await database;
    return await db.delete(
      tableName,
      where: 'id = ?',
      whereArgs: [id],
    );
  \\\\}
\\\\}

class User \\\\{
  final int? id;
  final String name;
  final String email;
  final DateTime createdAt;

  User(\\\\{
    this.id,
    required this.name,
    required this.email,
    required this.createdAt,
  \\\\});

  Map<String, dynamic> toMap() \\\\{
    return \\\\{
      'id': id,
      'name': name,
      'email': email,
      'created_at': createdAt.toIso8601String(),
    \\\\};
  \\\\}

  factory User.fromMap(Map<String, dynamic> map) \\\\{
    return User(
      id: map['id'],
      name: map['name'],
      email: map['email'],
      createdAt: DateTime.parse(map['created_at']),
    );
  \\\\}
\\\\}

Testing

Unit Testing

// test/counter_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter.dart';

void main() \\\\{
  group('Counter', () \\\\{
    test('should start with 0', () \\\\{
      final counter = Counter();
      expect(counter.value, 0);
    \\\\});

    test('should increment', () \\\\{
      final counter = Counter();
      counter.increment();
      expect(counter.value, 1);
    \\\\});

    test('should decrement', () \\\\{
      final counter = Counter();
      counter.increment();
      counter.decrement();
      expect(counter.value, 0);
    \\\\});
  \\\\});
\\\\}

Widget Testing

// test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';

void main() \\\\{
  testWidgets('Counter increments smoke test', (WidgetTester tester) async \\\\{
    // Build our app and trigger a frame.
    await tester.pumpWidget(MyApp());

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the '+' icon and trigger a frame.
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  \\\\});

  testWidgets('Should display error message when input is empty', (WidgetTester tester) async \\\\{
    await tester.pumpWidget(MaterialApp(home: LoginForm()));

    // Find the submit button and tap it without entering text
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();

    // Verify error message is displayed
    expect(find.text('Please enter your email'), findsOneWidget);
  \\\\});
\\\\}

Integration Testing

// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() \\\\{
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('end-to-end test', () \\\\{
    testWidgets('tap on the floating action button, verify counter', (tester) async \\\\{
      app.main();
      await tester.pumpAndSettle();

      // Verify the counter starts at 0.
      expect(find.text('0'), findsOneWidget);

      // Finds the floating action button to tap on.
      final Finder fab = find.byTooltip('Increment');

      // Emulate a tap on the floating action button.
      await tester.tap(fab);

      // Trigger a frame.
      await tester.pumpAndSettle();

      // Verify the counter increments by 1.
      expect(find.text('1'), findsOneWidget);
    \\\\});
  \\\\});
\\\\}

Build and Deployment

Android Build

# Debug build
flutter build apk --debug

# Release build
flutter build apk --release

# Build AAB (recommended for Play Store)
flutter build appbundle --release

# Install on device
flutter install

iOS Build

# Debug build
flutter build ios --debug

# Release build
flutter build ios --release

# Build for simulator
flutter build ios --simulator

Web Build

# Build for web
flutter build web

# Serve locally
flutter run -d chrome

Build configuration

# pubspec.yaml
flutter:
  assets:
    - assets/images/
    - assets/fonts/

  fonts:
    - family: CustomFont
      fonts:
        - asset: assets/fonts/CustomFont-Regular.ttf
        - asset: assets/fonts/CustomFont-Bold.ttf
          weight: 700

# Build flavors
flutter build apk --flavor production --cible lib/main_production.dart

Performance Optimization

Widget Optimization

// Use const constructors
const Text('Hello World')

// Use ListView.builder for large lists
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) \\\\{
    return ListTile(title: Text(items[index]));
  \\\\},
)

// Use RepaintBoundary for expensive widgets
RepaintBoundary(
  child: ExpensiveWidget(),
)

// Use AutomaticKeepAliveClientMixin for tabs
class MyTab extends StatefulWidget \\\\{
  @override
  _MyTabState createState() => _MyTabState();
\\\\}

class _MyTabState extends State<MyTab> with AutomaticKeepAliveClientMixin \\\\{
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) \\\\{
    super.build(context);
    return Container(/* tab content */);
  \\\\}
\\\\}

Image Optimization

// Use cached network images
CachedNetworkImage(
  imageUrl: 'https://exemple.com/image.jpg',
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)

// Optimize image loading
Image.network(
  'https://exemple.com/image.jpg',
  loadingBuilder: (context, child, loadingProgress) \\\\{
    if (loadingProgress == null) return child;
    return CircularProgressIndicator(
      value: loadingProgress.expectedTotalBytes != null
          ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
          : null,
    );
  \\\\},
)

Common commandes

Development

# Run app
flutter run

# Run on specific device
flutter run -d <device-id>

# Hot reload
r (in terminal)

# Hot restart
R (in terminal)

# Debug info
flutter doctor
flutter devices
flutter logs

Build and Test

# Run tests
flutter test

# Run integration tests
flutter test integration_test/

# Analyze code
flutter analyze

# Format code
flutter format .

# Clean project
flutter clean

# Get dependencies
flutter pub get

# Upgrade dependencies
flutter pub upgrade

Resources