Learn how to manage todo items in a Flutter app using the Provider package. This guide covers adding, updating, and deleting todos, along with handling edge cases and testing functionality.
Managing a list of todos is a classic example of CRUD (Create, Read, Update, Delete) operations in application development. In this section, we’ll explore how to implement these operations in a Flutter app using the Provider package. We’ll cover how to add new todos, update existing ones, delete them, and toggle their completion status. Additionally, we’ll discuss handling edge cases and testing the functionality to ensure a robust application.
Adding a new todo involves collecting data from the user, creating a Todo
object, and adding it to the list managed by the TodoProvider
. Here’s how you can achieve this:
To add a new todo, you typically need a user interface where users can enter the todo details. This could be a simple form with a TextField
for the todo title and an optional description.
class AddTodoScreen extends StatelessWidget {
final TextEditingController _titleController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Add Todo')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _titleController,
decoration: InputDecoration(labelText: 'Title'),
),
TextField(
controller: _descriptionController,
decoration: InputDecoration(labelText: 'Description'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
final newTodo = Todo(
id: DateTime.now().toString(),
title: _titleController.text,
description: _descriptionController.text,
isCompleted: false,
);
Provider.of<TodoProvider>(context, listen: false).addTodo(newTodo);
Navigator.pop(context);
},
child: Text('Add Todo'),
),
],
),
),
);
}
}
The TodoProvider
class should have a method to add a new todo to the list and notify listeners about the change.
class TodoProvider with ChangeNotifier {
List<Todo> _todos = [];
List<Todo> get todos => _todos;
void addTodo(Todo todo) {
_todos.add(todo);
notifyListeners();
}
}
Updating a todo involves passing the existing Todo
object to an edit screen, allowing the user to modify it, and then saving the changes back to the provider.
When navigating to the edit screen, pass the todo object that needs to be edited.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditTodoScreen(todo: todo),
),
);
The TodoProvider
should have a method to update an existing todo. This involves finding the todo by its ID, updating its details, and notifying listeners.
void updateTodo(Todo todo) {
int index = _todos.indexWhere((t) => t.id == todo.id);
if (index != -1) {
_todos[index] = todo;
notifyListeners();
}
}
class EditTodoScreen extends StatelessWidget {
final Todo todo;
final TextEditingController _titleController;
final TextEditingController _descriptionController;
EditTodoScreen({required this.todo})
: _titleController = TextEditingController(text: todo.title),
_descriptionController = TextEditingController(text: todo.description);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Edit Todo')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _titleController,
decoration: InputDecoration(labelText: 'Title'),
),
TextField(
controller: _descriptionController,
decoration: InputDecoration(labelText: 'Description'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
final updatedTodo = Todo(
id: todo.id,
title: _titleController.text,
description: _descriptionController.text,
isCompleted: todo.isCompleted,
);
Provider.of<TodoProvider>(context, listen: false).updateTodo(updatedTodo);
Navigator.pop(context);
},
child: Text('Update Todo'),
),
],
),
),
);
}
}
Deleting a todo can be achieved by providing a delete button or a swipe action in the UI. The TodoProvider
should have a method to remove a todo by its ID.
void deleteTodo(String id) {
_todos.removeWhere((todo) => todo.id == id);
notifyListeners();
}
You can add a delete button in the todo list item or implement a swipe-to-delete action.
ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key(todo.id),
onDismissed: (direction) {
Provider.of<TodoProvider>(context, listen: false).deleteTodo(todo.id);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${todo.title} deleted')),
);
},
background: Container(color: Colors.red),
child: ListTile(
title: Text(todo.title),
subtitle: Text(todo.description),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
Provider.of<TodoProvider>(context, listen: false).deleteTodo(todo.id);
},
),
),
);
},
)
Allow users to mark todos as complete or incomplete by updating the isCompleted
property and notifying listeners.
void toggleTodoCompletion(String id) {
int index = _todos.indexWhere((todo) => todo.id == id);
if (index != -1) {
_todos[index].isCompleted = !_todos[index].isCompleted;
notifyListeners();
}
}
You can use a checkbox or a toggle button in the todo list item.
ListTile(
title: Text(todo.title),
subtitle: Text(todo.description),
trailing: Checkbox(
value: todo.isCompleted,
onChanged: (value) {
Provider.of<TodoProvider>(context, listen: false).toggleTodoCompletion(todo.id);
},
),
)
Handling edge cases is crucial for providing a smooth user experience and preventing errors.
Thorough testing of all CRUD operations ensures the reliability of your application.
Write unit tests to verify that the TodoProvider
methods work as expected.
void main() {
group('TodoProvider', () {
test('adds a todo', () {
final provider = TodoProvider();
final todo = Todo(
id: '1',
title: 'Test Todo',
description: 'Test Description',
isCompleted: false,
);
provider.addTodo(todo);
expect(provider.todos.contains(todo), true);
});
test('updates a todo', () {
final provider = TodoProvider();
final todo = Todo(
id: '1',
title: 'Test Todo',
description: 'Test Description',
isCompleted: false,
);
provider.addTodo(todo);
final updatedTodo = Todo(
id: '1',
title: 'Updated Todo',
description: 'Updated Description',
isCompleted: true,
);
provider.updateTodo(updatedTodo);
expect(provider.todos.first.title, 'Updated Todo');
expect(provider.todos.first.isCompleted, true);
});
test('deletes a todo', () {
final provider = TodoProvider();
final todo = Todo(
id: '1',
title: 'Test Todo',
description: 'Test Description',
isCompleted: false,
);
provider.addTodo(todo);
provider.deleteTodo(todo.id);
expect(provider.todos.contains(todo), false);
});
});
}
Managing todos in a Flutter app using the Provider package involves implementing CRUD operations effectively. By understanding how to add, update, delete, and toggle todos, you can create a responsive and user-friendly application. Handling edge cases and testing functionality are crucial steps in ensuring the robustness of your app. As you implement these features, consider the user experience and strive for a seamless interaction with your application.