Hello, Flutter developers! In this post, we’ll dive deep into improving state management in our Flutter app by introducing the Provider library. We’ll enhance our To-Do list app to make it more efficient and scalable.
1. The Importance of State Management
First, let’s understand why state management is crucial. As apps grow and become more complex, managing data (state) shared across multiple widgets becomes challenging. Effective state management offers these benefits:
- Improved code readability and maintainability
- Performance optimization
- Reduced likelihood of bugs
- Ease of feature expansion
2. Introducing the Provider Package
Provider is one of the state management solutions recommended by the Flutter team. This simple yet powerful library offers the following advantages:
- Easy to use
- Integrates naturally with Flutter’s widget tree
- Excellent performance
- Easy to test
3. Installing Provider
Let’s add Provider to our project:
- Open the
pubspec.yaml
file. - Add the following line in the
dependencies
section:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
- Run the following command in your terminal:
flutter pub get
This installs the Provider package in your project.
4. Creating the Model Class
Now, let’s create our app’s data model. Create a models
folder inside the lib
folder, and within it, create a todo_model.dart
file:
import 'package:flutter/foundation.dart';
class Todo {
String id;
String title;
bool isCompleted;
Todo({required this.id, required this.title, this.isCompleted = false});
}
class TodoModel extends ChangeNotifier {
List<Todo> _todos = [];
List<Todo> get todos => _todos;
void addTodo(Todo todo) {
_todos.add(todo);
notifyListeners();
}
void toggleTodo(String id) {
final todo = _todos.firstWhere((todo) => todo.id == id);
todo.isCompleted = !todo.isCompleted;
notifyListeners();
}
void deleteTodo(String id) {
_todos.removeWhere((todo) => todo.id == id);
notifyListeners();
}
}
Let’s break down this code:
- The
Todo
class represents each todo item. - The
TodoModel
class extendsChangeNotifier
to notify state changes. _todos
is a private variable storing all todo items.addTodo
,toggleTodo
, anddeleteTodo
methods change the state and callnotifyListeners()
to notify the UI of changes.
5. Setting Up Provider
Now, let’s modify the main.dart
file to set up Provider:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/todo_model.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => TodoModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TodoListScreen(),
);
}
}
We use ChangeNotifierProvider
to provide TodoModel
at the top level of our app. This allows access to this model from any widget in the app.
6. Updating the UI
Now, let’s modify the TodoListScreen
widget to use Provider:
class TodoListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todo List'),
),
body: Consumer<TodoModel>(
builder: (context, todoModel, child) {
return ListView.builder(
itemCount: todoModel.todos.length,
itemBuilder: (context, index) {
final todo = todoModel.todos[index];
return ListTile(
title: Text(todo.title),
leading: Checkbox(
value: todo.isCompleted,
onChanged: (_) => todoModel.toggleTodo(todo.id),
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => todoModel.deleteTodo(todo.id),
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _addTodo(context),
child: Icon(Icons.add),
),
);
}
void _addTodo(BuildContext context) {
showDialog(
context: context,
builder: (context) {
String newTodoTitle = '';
return AlertDialog(
title: Text('Add a new todo'),
content: TextField(
onChanged: (value) {
newTodoTitle = value;
},
),
actions: <Widget>[
TextButton(
child: Text('Add'),
onPressed: () {
if (newTodoTitle.isNotEmpty) {
Provider.of<TodoModel>(context, listen: false).addTodo(
Todo(id: DateTime.now().toString(), title: newTodoTitle),
);
Navigator.of(context).pop();
}
},
),
],
);
},
);
}
}
Let’s break down this code:
- We use
Consumer<TodoModel>
to detect changes inTodoModel
and update the UI. ListView.builder
is used to efficiently display the todo list.- Each
ListTile
allows changing or deleting a todo’s state via a checkbox and delete button. - The
FloatingActionButton
can be pressed to add a new todo.
7. Implementing the Add Todo Feature
The _addTodo
method works as follows:
- It displays a dialog box asking the user to input a title for the new todo.
- When the user presses the ‘Add’ button, it creates a new
Todo
object with the entered title. - It uses
Provider.of<TodoModel>(context, listen: false)
to get an instance ofTodoModel
. - It calls the
addTodo
method to add the new todo.
Conclusion
We’ve now improved the state management of our To-Do list app using Provider. The app’s state is now centrally managed, and the UI automatically updates according to changes in this state.
This structure greatly enhances the app’s scalability and makes it easier to add new features. For example, we could easily add features like editing todo items or filtering completed items.
In our next post, we’ll add data persistence to this app so that data is retained even when the app is closed and reopened.
If you have any questions about state management using Flutter and Provider, feel free to leave a comment. Let’s learn and grow together!
Related Resources
- Provider package: https://pub.dev/packages/provider
- Flutter state management documentation: https://flutter.dev/docs/development/data-and-backend/state-mgmt
- Provider official documentation: https://pub.dev/documentation/provider/latest/
Related Posts
Flutter Development-2: Project Creation and Structure Understanding – CSAI
Flutter Development-3: Implementing a To-Do List App UI – CSAI
Flutter Development-4: Adding App State Management – CSAI
Flutter Development-5: Adding Local Storage – CSAI
Flutter development-6: Enhancing UI and Features – CSAI