🧭 Overview
In this guide, we’ll build a clean and simple Todo list app using Flutter and the Provider package. This is a perfect way to learn state management, Flutter widgets, and best practices — all in one.
📱 What You’ll Build
A minimal but functional app that allows users to:
- Add todos
- See a list of tasks
- Remove completed tasks
🤔 Why Use Provider?
Provider
is a lightweight state management tool built by the Flutter team. It helps you:
- Share app data across widgets
- Update UI automatically when data changes
- Keep logic out of your UI
⚙️ Project Setup
Create a new Flutter project:
flutter create todo_provider
cd todo_provider
Add provider
to your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
provider: ^6.1.0
Run:
flutter pub get
🧩 Defining the Todo Model
Let’s start by creating a simple model.
Create lib/models/todo.dart
:
class Todo {
final String id;
final String title;
bool isDone;
Todo({required this.id, required this.title, this.isDone = false});
}
🧠 Creating the Provider Class
Create lib/providers/todo_provider.dart
:
import 'package:flutter/material.dart';
import '../models/todo.dart';
class TodoProvider with ChangeNotifier {
List<Todo> _todos = [];
List<Todo> get todos => _todos;
void addTodo(String title) {
final todo = Todo(
id: DateTime.now().toString(),
title: title,
);
_todos.add(todo);
notifyListeners();
}
void toggleTodo(String id) {
final index = _todos.indexWhere((todo) => todo.id == id);
_todos[index].isDone = !_todos[index].isDone;
notifyListeners();
}
void deleteTodo(String id) {
_todos.removeWhere((todo) => todo.id == id);
notifyListeners();
}
}
🎨 Building the UI
Update main.dart
to include the provider:
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => TodoProvider(),
child: MyApp(),
),
);
}
Basic UI:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Todo',
home: TodoScreen(),
);
}
}
Create lib/screens/todo_screen.dart
:
class TodoScreen extends StatelessWidget {
final TextEditingController _controller = TextEditingController();
@override
Widget build(BuildContext context) {
final todoProvider = Provider.of<TodoProvider>(context);
return Scaffold(
appBar: AppBar(title: Text('My Todos')),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(controller: _controller),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
if (_controller.text.isNotEmpty) {
todoProvider.addTodo(_controller.text);
_controller.clear();
}
},
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: todoProvider.todos.length,
itemBuilder: (ctx, i) {
final todo = todoProvider.todos[i];
return ListTile(
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isDone ? TextDecoration.lineThrough : null,
),
),
leading: Checkbox(
value: todo.isDone,
onChanged: (_) => todoProvider.toggleTodo(todo.id),
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => todoProvider.deleteTodo(todo.id),
),
);
},
),
),
],
),
);
}
}
✅ Complete Source Code
You can find the full working code on GitHub
🔗 Next Steps
From here, you can:
- Add persistent storage using
shared_preferences
orhive
- Improve UI with
flutter_hooks
orriverpod
- Turn this into a multi-screen app
👉 Continue with:
How to Save Flutter Todo Data Using Shared Preferences
📬 Subscribe for More
Want full tutorials, real-world app examples, and Flutter news?
Join the FlutterTalk Newsletter →
What is Provider in Flutter?
Provider is a state management library that allows Flutter widgets to react to data changes efficiently.
Is this Todo app production-ready?
It’s a basic prototype. For production, add error handling, persistent storage, and testing.