Introduction
Flutter, Google's open-source framework, is revolutionizing mobile development in 2026 with its ultra-performant native rendering and declarative UI paradigm. Unlike hybrid frameworks like React Native, Flutter compiles to native ARM code, delivering performance on par with Swift or Kotlin—all from a single codebase for iOS, Android, web, and desktop.
Why adopt it now? In 2026, 70% of Fortune 500 apps use Flutter for its rapid development (hot reload in <1s) and rich widget kit (Material 3, Cupertino). This beginner tutorial guides you step by step to create an evolving Counter app into a Todo List, with 100% copy-pasteable code. By the end, you'll master the basics: stateless/stateful widgets, simple state management, navigation, and deployment. Ready to boost your mobile dev career? (128 words)
Prerequisites
- Computer with macOS, Windows, or Linux (8 GB RAM min.)
- Flutter SDK version 3.24+ (2026 stable)
- Android Studio (for Android emulator) or Xcode (macOS for iOS)
- VS Code with Flutter and Dart extensions
- Basic programming knowledge (no Dart experience required—we'll cover it along the way)
Install and Verify Flutter
git clone https://github.com/flutter/flutter.git -b stable
export PATH="$PATH:`pwd`/flutter/bin"
flutter precache
flutter doctor -vThese commands clone the stable Flutter SDK, add it to your PATH, and verify the installation. flutter doctor lists any missing tools (e.g., Android SDK). Stick to stable versions as a beginner; restart your terminal after setting the PATH.
Create Your First Project
Analogy: Creating a Flutter project is like initializing a new Git repo, but it generates a ready-to-code app skeleton. This includes lib/main.dart (entry point), pubspec.yaml (dependencies), and iOS/Android configs.
Generate the Project
flutter create ma_premiere_app
cd ma_premiere_app
flutter pub getflutter create scaffolds a complete project with default Material Design. pub get resolves dependencies. Run flutter run to test on an emulator; if you hit errors, check flutter doctor.
Basic App: main.dart
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: 'Ma Première App',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MyHomePage(title: 'Accueil'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Vous avez appuyé tant de fois:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}This code creates a complete Counter app: runApp launches the app, MaterialApp handles theme and routing, StatefulWidget updates the UI via setState. Copy-paste into lib/main.dart, then run flutter run. Tip: Use const for performance; key prevents unnecessary rebuilds.
Understanding Widgets
Flutter is built on immutable widgets: everything is a widget (UI + logic). Use Stateless for static content (e.g., icons), Stateful for dynamic (e.g., counter). The widget tree rebuilds on every setState, like a reactive DOM but faster.
Add a ListView
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: 'Ma App ListView',
theme: ThemeData(primarySwatch: Colors.green),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Ma ListView')),
body: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
leading: const Icon(Icons.star),
title: Text('Item $index'),
subtitle: Text('Sous-titre $index'),
trailing: const Icon(Icons.arrow_forward_ios),
);
},
),
);
}
}Replace the body with ListView.builder for an optimized infinite list (lazy loading). itemBuilder generates items dynamically; itemCount sets the limit. Perfect for long lists. Avoid fixed ListView for better mobile performance.
Todo List with State
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: 'Todo App',
theme: ThemeData(primarySwatch: Colors.orange),
home: const TodoListScreen(),
);
}
}
class TodoListScreen extends StatefulWidget {
const TodoListScreen({super.key});
@override
State<TodoListScreen> createState() => _TodoListScreenState();
}
class _TodoListScreenState extends State<TodoListScreen> {
final List<String> _todos = [];
final TextEditingController _controller = TextEditingController();
void _addTodo() {
if (_controller.text.isNotEmpty) {
setState(() {
_todos.add(_controller.text);
_controller.clear();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Mes Todos')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(hintText: 'Nouvelle todo'),
),
),
IconButton(
onPressed: _addTodo,
icon: const Icon(Icons.add),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) => ListTile(
title: Text(_todos[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => setState(() => _todos.removeAt(index)),
),
),
),
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}This Todo App manages state with List, TextEditingController for input, and dispose for cleanup. setState triggers targeted rebuilds. Adds and removes items dynamically. Pitfall: Forgetting dispose causes memory leaks; use Expanded for smooth scrolling.
Run the App
flutter run -d chrome # Pour web
# ou
flutter run # Pour émulateur connecté
# Hot reload: tapez 'r'
# Hot restart: 'R'flutter run builds and launches on a device, emulator, or web. Use -d chrome for quick web testing. Hot reload preserves state. Check flutter devices to list available targets.
Best Practices
- Hot Reload: Save (Ctrl+S) to iterate quickly, but use Hot Restart for state changes.
- Use
consteverywhere to optimize rebuilds. - State Management: setState for beginners; upgrade to Provider/Riverpod for complex apps.
- Test on real devices: Emulators are slow for animations.
- Pubspec.yaml: Add deps like
http: ^1.2.0for APIs, thenflutter pub get.
Common Errors to Avoid
- Indentation/missing {}: Dart is strict; use VS Code auto-format (Ctrl+Shift+I).
- No setState: UI won't update (classic beginner mistake).
- Undisposed controller: Memory leaks on Android/iOS.
- Forgot flutter clean: After pubspec changes, run
flutter clean && flutter pub get.
Next Steps
- Official Flutter Docs
- Interactive Codelabs
- Master Riverpod for state: Advanced Tutorial
- Check out our Flutter Learni Courses for pros (advanced widgets, Firebase, Play Store deployment).