Flutter-7: Introducing State Management

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:

  1. Open the pubspec.yaml file.
  2. Add the following line in the dependencies section:
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0
  1. 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 extends ChangeNotifier to notify state changes.
  • _todos is a private variable storing all todo items.
  • addTodo, toggleTodo, and deleteTodo methods change the state and call notifyListeners() 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 in TodoModel 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:

  1. It displays a dialog box asking the user to input a title for the new todo.
  2. When the user presses the ‘Add’ button, it creates a new Todo object with the entered title.
  3. It uses Provider.of<TodoModel>(context, listen: false) to get an instance of TodoModel.
  4. 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

Related Posts

Flutter development-1 – CSAI

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

Leave a Reply

Your email address will not be published. Required fields are marked *