Skip to main content

Code that compiles on the first try! CodeWithMpia wishes you very happy holidays.✨

Aller au contenu principal

Débuter avec Flutter: ton guide complet pour créer des apps mobiles

Ce guide te permet de bien débuter avec Flutter et Dart : installation de l’environnement, création d’un premier projet, bases du langage, widgets stateless/stateful, navigation, formulaires, requêtes HTTP et bonnes pratiques pour structurer tes apps mobiles iOS/Android.

M
Mpia
12/17/2025 26 min
123 vues
0 comme.
#Dart#Flutter#Mobile
Débuter avec Flutter: ton guide complet pour créer des apps mobiles

Salut ! Si tu veux te lancer dans le développement d'applications mobiles, tu es au bon endroit. Aujourd'hui, on va parler de Flutter, le framework de Google qui te permet de créer des apps iOS et Android avec un seul code.

Pourquoi Flutter ? Parce qu'il est rapide, moderne, et qu'il te permet de créer des apps magnifiques sans te prendre la tête avec deux bases de code différentes. En 2025, Flutter est devenu un incontournable du développement mobile.

Prêt à créer ta première app ? C'est parti ! 🚀

Pourquoi choisir Flutter ?

Laisse-moi te convaincre si tu hésites encore :

Avantages de Flutter

  • Un seul code pour iOS et Android : écris une fois, déploie partout

  • Performance native : pas de ralentissement, Flutter compile directement en natif

  • Hot Reload : vois tes modifications instantanément sans recompiler

  • UI magnifique : widgets Material Design et Cupertino prêts à l'emploi

  • Communauté active : des milliers de packages disponibles

  • Dart moderne : un langage propre et facile à apprendre

Apps connues créées avec Flutter

  • Google Ads

  • Alibaba

  • BMW

  • eBay

Si ça marche pour eux, ça marchera pour toi !

Installation et configuration

1. Installer Flutter

# macOS (avec Homebrew)
brew install --cask flutter

# Linux
git clone https://github.com/flutter/flutter.git -b stable
export PATH="$PATH:`pwd`/flutter/bin"

# Vérifier l'installation
flutter doctor

flutter doctor va te dire ce qui manque. Suis les instructions pour installer les dépendances manquantes.

2. Configurer un émulateur

# Lister les émulateurs disponibles
flutter emulators

# Créer un émulateur Android
flutter emulators --create

# Lancer l'émulateur
flutter emulators --launch <emulator_id>

3. Ton premier projet

# Créer un nouveau projet
flutter create ma_premiere_app

# Aller dans le dossier
cd ma_premiere_app

# Lancer l'app
flutter run

Boom ! Tu viens de lancer ta première app Flutter ! 🎉

Structure d'un projet Flutter

Regardons ce qu'on a :

ma_premiere_app/
├── lib/
│   └── main.dart          # Point d'entrée de l'app
├── android/               # Code spécifique Android
├── ios/                   # Code spécifique iOS
├── test/                  # Tests
├── pubspec.yaml          # Dépendances et config
└── README.md

Le fichier le plus important : lib/main.dart - c'est là que tout commence !

Les bases de Dart

Avant Flutter, un petit tour rapide de Dart :

// Variables
var nom = 'Mpia';           // Type inféré
String prenom = 'M.';       // Type explicite
int age = 25;
double taille = 1.75;
bool estDev = true;

// Const et final
const PI = 3.14159;         // Compile-time constant
final maintenant = DateTime.now(); // Runtime constant

// Listes
var fruits = ['Pomme', 'Banane', 'Orange'];
fruits.add('Mangue');

// Maps
var personne = {
  'nom': 'Mpia',
  'age': 25,
  'estDev': true,
};

// Fonctions
String direBonjour(String nom) {
  return 'Bonjour $nom !';
}

// Fonction fléchée
int doubler(int x) => x * 2;

// Fonction avec paramètres nommés
void afficherInfo({required String nom, int? age}) {
  print('Nom: $nom, Age: ${age ?? 'inconnu'}');
}

// Classes
class Utilisateur {
  final String nom;
  final int age;

  Utilisateur({required this.nom, required this.age});

  void sePresenter() {
    print('Je suis $nom, j\'ai $age ans');
  }
}

// Null safety
String? nomOptional;  // Peut être null
String nomRequis = 'Mpia';  // Ne peut pas être null

// Gestion du null
int longueur = nomOptional?.length ?? 0;  // 0 si null

Ta première app Flutter

Remplaçons le code par défaut par quelque chose de simple :

import 'package:flutter/material.dart';

void main() {
  runApp(MonApp());
}

class MonApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ma Première App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: PageAccueil(),
    );
  }
}

class PageAccueil extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bienvenue'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Bonjour Flutter !',
              style: TextStyle(
                fontSize: 32,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                print('Bouton cliqué !');
              },
              child: Text('Clique-moi'),
            ),
          ],
        ),
      ),
    );
  }
}

Sauvegarde et regarde le Hot Reload faire son magic ! ✨

Widgets : les briques de Flutter

Tout dans Flutter est un widget. Voici les plus importants :

Widgets de structure

// Container - la boîte à tout faire
Container(
  width: 200,
  height: 100,
  padding: EdgeInsets.all(16),
  margin: EdgeInsets.symmetric(vertical: 8),
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [
      BoxShadow(
        color: Colors.black26,
        blurRadius: 8,
        offset: Offset(0, 4),
      ),
    ],
  ),
  child: Text('Un container stylé'),
)

// Row - disposition horizontale
Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Icon(Icons.home),
    Icon(Icons.favorite),
    Icon(Icons.settings),
  ],
)

// Column - disposition verticale
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('Titre'),
    Text('Sous-titre'),
    Text('Description'),
  ],
)

// Stack - superposition
Stack(
  children: [
    Image.network('https://example.com/image.jpg'),
    Positioned(
      bottom: 16,
      right: 16,
      child: FloatingActionButton(
        onPressed: () {},
        child: Icon(Icons.edit),
      ),
    ),
  ],
)

// ListView - liste défilante
ListView(
  children: [
    ListTile(
      leading: Icon(Icons.person),
      title: Text('Utilisateur 1'),
      subtitle: Text('email@example.com'),
      trailing: Icon(Icons.chevron_right),
      onTap: () {
        print('Utilisateur cliqué');
      },
    ),
    // Autres items...
  ],
)

Widgets interactifs

// TextField - saisie de texte
TextField(
  decoration: InputDecoration(
    labelText: 'Nom',
    hintText: 'Entrez votre nom',
    prefixIcon: Icon(Icons.person),
    border: OutlineInputBorder(),
  ),
  onChanged: (value) {
    print('Texte saisi: $value');
  },
)

// Checkbox
Checkbox(
  value: true,
  onChanged: (bool? value) {
    print('Checkbox: $value');
  },
)

// Switch
Switch(
  value: false,
  onChanged: (bool value) {
    print('Switch: $value');
  },
)

// Slider
Slider(
  value: 50,
  min: 0,
  max: 100,
  divisions: 10,
  label: '50',
  onChanged: (double value) {
    print('Slider: $value');
  },
)

StatefulWidget : gérer l'état

Pour les widgets qui changent, on utilise StatefulWidget :

class Compteur extends StatefulWidget {
  @override
  _CompteurState createState() => _CompteurState();
}

class _CompteurState extends State<Compteur> {
  int _compteur = 0;

  void _incrementer() {
    setState(() {
      _compteur++;
    });
  }

  void _decrementer() {
    setState(() {
      _compteur--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Compteur'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Valeur du compteur:',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '$_compteur',
              style: TextStyle(
                fontSize: 64,
                fontWeight: FontWeight.bold,
                color: Colors.blue,
              ),
            ),
            SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                  onPressed: _decrementer,
                  child: Icon(Icons.remove),
                  heroTag: 'decrement',
                ),
                SizedBox(width: 20),
                FloatingActionButton(
                  onPressed: _incrementer,
                  child: Icon(Icons.add),
                  heroTag: 'increment',
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
// Page 1
class PageUn extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page 1')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Naviguer vers la page 2
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => PageDeux()),
            );
          },
          child: Text('Aller à la page 2'),
        ),
      ),
    );
  }
}

// Page 2
class PageDeux extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page 2')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Retour à la page précédente
            Navigator.pop(context);
          },
          child: Text('Retour'),
        ),
      ),
    );
  }
}

// Navigation avec paramètres
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => PageDetail(id: 123),
  ),
);

// Navigation nommée (dans MaterialApp)
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => PageAccueil(),
    '/profile': (context) => PageProfile(),
    '/settings': (context) => PageSettings(),
  },
);

// Utiliser les routes nommées
Navigator.pushNamed(context, '/profile');

Liste dynamique avec ListView.builder

Pour afficher des listes longues efficacement :

class ListeUtilisateurs extends StatelessWidget {
  final List<String> utilisateurs = List.generate(
    100,
    (index) => 'Utilisateur ${index + 1}',
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Liste d\'utilisateurs'),
      ),
      body: ListView.builder(
        itemCount: utilisateurs.length,
        itemBuilder: (context, index) {
          return Card(
            margin: EdgeInsets.symmetric(
              horizontal: 16,
              vertical: 8,
            ),
            child: ListTile(
              leading: CircleAvatar(
                child: Text('${index + 1}'),
              ),
              title: Text(utilisateurs[index]),
              subtitle: Text('email${index + 1}@example.com'),
              trailing: Icon(Icons.chevron_right),
              onTap: () {
                // Action au clic
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text('Cliqué sur ${utilisateurs[index]}'),
                    duration: Duration(seconds: 1),
                  ),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

Formulaire complet

class FormulaireInscription extends StatefulWidget {
  @override
  _FormulaireInscriptionState createState() => _FormulaireInscriptionState();
}

class _FormulaireInscriptionState extends State<FormulaireInscription> {
  final _formKey = GlobalKey<FormState>();
  final _nomController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  bool _accepteConditions = false;

  @override
  void dispose() {
    _nomController.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _soumettre() {
    if (_formKey.currentState!.validate()) {
      if (!_accepteConditions) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Veuillez accepter les conditions')),
        );
        return;
      }

      // Traiter le formulaire
      print('Nom: ${_nomController.text}');
      print('Email: ${_emailController.text}');

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Inscription réussie !')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Inscription'),
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextFormField(
                controller: _nomController,
                decoration: InputDecoration(
                  labelText: 'Nom complet',
                  prefixIcon: Icon(Icons.person),
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Veuillez entrer votre nom';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),

              TextFormField(
                controller: _emailController,
                decoration: InputDecoration(
                  labelText: 'Email',
                  prefixIcon: Icon(Icons.email),
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Veuillez entrer votre email';
                  }
                  if (!value.contains('@')) {
                    return 'Email invalide';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),

              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(
                  labelText: 'Mot de passe',
                  prefixIcon: Icon(Icons.lock),
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Veuillez entrer un mot de passe';
                  }
                  if (value.length < 6) {
                    return 'Le mot de passe doit faire au moins 6 caractères';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),

              CheckboxListTile(
                title: Text('J\'accepte les conditions d\'utilisation'),
                value: _accepteConditions,
                onChanged: (bool? value) {
                  setState(() {
                    _accepteConditions = value ?? false;
                  });
                },
                controlAffinity: ListTileControlAffinity.leading,
              ),
              SizedBox(height: 24),

              ElevatedButton(
                onPressed: _soumettre,
                style: ElevatedButton.styleFrom(
                  padding: EdgeInsets.symmetric(vertical: 16),
                ),
                child: Text(
                  'S\'inscrire',
                  style: TextStyle(fontSize: 18),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Ajouter des dépendances

Le fichier pubspec.yaml gère les dépendances :

dependencies:
  flutter:
    sdk: flutter

  # Package HTTP pour les requêtes
  http: ^1.1.0

  # Provider pour la gestion d'état
  provider: ^6.1.1

  # SharedPreferences pour le stockage local
  shared_preferences: ^2.2.2

  # Package d'icônes
  font_awesome_flutter: ^10.6.0

Après avoir modifié pubspec.yaml :

flutter pub get

Exemple : requête HTTP

import 'package:http/http.dart' as http;
import 'dart:convert';

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

  // GET request
  static Future<List<dynamic>> recupererPosts() async {
    final response = await http.get(Uri.parse('$baseUrl/posts'));

    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('Erreur lors du chargement');
    }
  }

  // POST request
  static Future<Map<String, dynamic>> creerPost({
    required String title,
    required String body,
  }) async {
    final response = await http.post(
      Uri.parse('$baseUrl/posts'),
      headers: {'Content-Type': 'application/json'},
      body: json.encode({
        'title': title,
        'body': body,
        'userId': 1,
      }),
    );

    if (response.statusCode == 201) {
      return json.decode(response.body);
    } else {
      throw Exception('Erreur lors de la création');
    }
  }
}

// Utilisation dans un widget
class ListePosts extends StatefulWidget {
  @override
  _ListePostsState createState() => _ListePostsState();
}

class _ListePostsState extends State<ListePosts> {
  List<dynamic> posts = [];
  bool isLoading = true;

  @override
  void initState() {
    super.initState();
    chargerPosts();
  }

  Future<void> chargerPosts() async {
    try {
      final data = await ApiService.recupererPosts();
      setState(() {
        posts = data;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Erreur: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    if (isLoading) {
      return Center(child: CircularProgressIndicator());
    }

    return ListView.builder(
      itemCount: posts.length,
      itemBuilder: (context, index) {
        final post = posts[index];
        return ListTile(
          title: Text(post['title']),
          subtitle: Text(post['body']),
        );
      },
    );
  }
}

Bonnes pratiques

1. Organisation des fichiers

lib/
├── main.dart
├── models/
   ├── user.dart
   └── post.dart
├── screens/
   ├── home_screen.dart
   ├── profile_screen.dart
   └── settings_screen.dart
├── widgets/
   ├── custom_button.dart
   └── user_card.dart
├── services/
   ├── api_service.dart
   └── auth_service.dart
└── utils/
    ├── constants.dart
    └── helpers.dart

2. Extraire les widgets

// ❌ Mauvais - tout dans build()
Widget build(BuildContext context) {
  return Column(
    children: [
      Container(
        padding: EdgeInsets.all(16),
        child: Text('Titre'),
      ),
      Container(
        padding: EdgeInsets.all(16),
        child: Text('Description'),
      ),
    ],
  );
}

// ✅ Bon - widgets extraits
Widget build(BuildContext context) {
  return Column(
    children: [
      _buildTitre(),
      _buildDescription(),
    ],
  );
}

Widget _buildTitre() {
  return Container(
    padding: EdgeInsets.all(16),
    child: Text('Titre'),
  );
}

3. Utiliser const quand possible

// Améliore les performances
const Text('Texte constant')
const Icon(Icons.home)
const SizedBox(height: 20)

4. Nommer les routes

// Mieux que d'utiliser MaterialPageRoute partout
class Routes {
  static const String home = '/';
  static const String profile = '/profile';
  static const String settings = '/settings';
}

Déboguer efficacement

// Print dans la console
print('Valeur: $value');

// Debugger avec breakpoints
// Ajoute un breakpoint dans ton IDE et lance en mode debug

// Inspector de widgets
// Cmd/Ctrl + Shift + I pour ouvrir DevTools

// Hot Reload
// Sauvegarde ou appuie sur 'r' dans le terminal

// Hot Restart (si Hot Reload ne suffit pas)
// Appuie sur 'R' dans le terminal

Prochaines étapes

Maintenant que tu as les bases, voici quoi explorer ensuite :

  1. Gestion d'état avancée : Provider, Riverpod, ou Bloc

  2. Animations : AnimatedContainer, Hero animations

  3. Base de données locale : SQLite, Hive

  4. Firebase : Authentication, Firestore, Cloud Storage

  5. Tests : Unit tests, Widget tests, Integration tests

  6. Publication : Play Store et App Store

Conclusion

Flutter est un framework incroyable pour créer des apps mobiles. Avec un seul code, tu peux cibler iOS et Android, et le Hot Reload rend le développement super agréable.

Les points clés à retenir :

  • Tout est widget : comprends bien ce concept

  • StatelessWidget pour ce qui ne change pas

  • StatefulWidget pour ce qui change

  • setState() pour mettre à jour l'UI

  • Navigation avec Navigator

  • Layouts avec Row, Column, Stack

N'hésite pas à expérimenter, casser des trucs, et surtout à t'amuser. C'est comme ça qu'on apprend le mieux !


Ressources pour aller plus loin :

Tu as créé ta première app Flutter ? Partage ton expérience dans les commentaires!

Commentaires (0)

Laisser un commentaire

Aucun commentaire pour le moment. Soyez le premier à commenter !