Explore how to build reactive UI components using MobX in Flutter, focusing on Observers and their integration with UI elements.
In this section, we will delve into the practical implementation of UI components using MobX for state management in Flutter. The focus will be on creating a responsive user interface that reacts to state changes using Observer
widgets. We will build a task management application to demonstrate these concepts.
The task list UI is the heart of our application, where users can view, add, and manage their tasks. We’ll use MobX to ensure that any changes in the task list are immediately reflected in the UI.
The HomeScreen
will display a list of tasks. We’ll use an Observer
widget to ensure that any changes in the task list are automatically updated in the UI.
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:your_app/stores/task_store.dart';
import 'package:your_app/widgets/task_item.dart';
class HomeScreen extends StatelessWidget {
final TaskStore taskStore;
HomeScreen({required this.taskStore});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Task Manager')),
body: Observer(
builder: (_) => ListView.builder(
itemCount: taskStore.taskList.length,
itemBuilder: (context, index) {
final task = taskStore.taskList[index];
return TaskItem(
task: task,
onToggle: () => taskStore.toggleTaskStatus(task.id),
onDelete: () => taskStore.removeTask(task.id),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddTaskScreen(taskStore: taskStore),
),
);
},
child: Icon(Icons.add),
),
);
}
}
Key Points:
Observer
widget listens to changes in the taskStore.taskList
and rebuilds the ListView
whenever the list changes.FloatingActionButton
is used to navigate to the AddTaskScreen
, where users can add new tasks.Each task is represented by a TaskItem
widget. This widget displays task details and provides options to toggle completion status or delete the task.
import 'package:flutter/material.dart';
import 'package:your_app/models/task.dart';
class TaskItem extends StatelessWidget {
final Task task;
final VoidCallback onToggle;
final VoidCallback onDelete;
TaskItem({
required this.task,
required this.onToggle,
required this.onDelete,
});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(task.title),
subtitle: Text(task.description),
leading: Checkbox(
value: task.isCompleted,
onChanged: (_) => onToggle(),
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: onDelete,
),
);
}
}
Key Points:
Checkbox
widget allows users to mark tasks as completed or pending.IconButton
provides a delete option for each task.To add new tasks, we will create an AddTaskScreen
with a form. This screen will use TextEditingController
to capture user input.
import 'package:flutter/material.dart';
import 'package:your_app/stores/task_store.dart';
import 'package:your_app/models/task.dart';
class AddTaskScreen extends StatelessWidget {
final TaskStore taskStore;
final TextEditingController titleController = TextEditingController();
final TextEditingController descriptionController = TextEditingController();
AddTaskScreen({required this.taskStore});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Add New Task')),
body: Padding(
padding: 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 task = Task(
id: DateTime.now().toString(),
title: titleController.text,
description: descriptionController.text,
isCompleted: false,
);
taskStore.addTask(task);
Navigator.pop(context);
},
child: Text('Add Task'),
),
],
),
),
);
}
}
Key Points:
TextField
widgets capture the task title and description.Task
object is created and added to the taskStore
.Navigation is handled using Flutter’s Navigator
class. We pass the taskStore
to the AddTaskScreen
to maintain a single source of truth for the task list.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddTaskScreen(taskStore: taskStore),
),
);
Key Points:
Navigator.push
to navigate to the AddTaskScreen
.Navigator.pop
to return to the previous screen after adding a task.To enhance the user experience, we can add filtering options to view all, completed, or pending tasks. This can be achieved using computed properties in the MobX store.
// In TaskStore
@computed
List<Task> get completedTasks => taskList.where((task) => task.isCompleted).toList();
@computed
List<Task> get pendingTasks => taskList.where((task) => !task.isCompleted).toList();
Key Points:
Observer
widgets only where necessary to optimize performance and avoid unnecessary rebuilds.For a complete codebase, refer to the GitHub repository containing all the examples discussed.
To better understand the flow of data and user interactions, consider the following diagram:
graph TD; A[User Interaction] --> B[TaskStore]; B --> C[Observer Widget]; C --> D[UI Update]; B --> E[Computed Properties]; E --> C;
Explanation:
TaskStore
.Observer
widget listens for changes and updates the UI accordingly.By following these guidelines, you can create a robust and responsive task management application using MobX in Flutter. This approach not only simplifies state management but also enhances the user experience by ensuring that the UI is always in sync with the underlying data.