In this section, we will explore the Provider package, a popular state management solution in Flutter. The Provider package is built on top of InheritedWidget and offers a simple and efficient way to manage state in your Flutter applications.

What is the Provider Package?

The Provider package is a wrapper around InheritedWidget, making it easier to manage and propagate state changes throughout your widget tree. It helps in:

  • Decoupling business logic from UI code.
  • Simplifying state management.
  • Improving code readability and maintainability.

Key Concepts

  1. Provider

The core class that allows you to expose a value to the widget tree. It listens for changes and rebuilds the widgets that depend on the provided value.

  1. ChangeNotifier

A class that provides change notifications to its listeners. It is commonly used with Provider to notify widgets about state changes.

  1. Consumer

A widget that listens to changes in the provided value and rebuilds itself when the value changes.

Setting Up Provider

To use the Provider package, you need to add it to your pubspec.yaml file:

dependencies:
  provider: ^6.0.0

Then, run flutter pub get to install the package.

Basic Example

Let's create a simple counter app using the Provider package.

Step 1: Create a ChangeNotifier Class

First, create a class that extends ChangeNotifier to manage the state:

import 'package:flutter/foundation.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

Step 2: Provide the ChangeNotifier

Wrap your app with a ChangeNotifierProvider to provide the Counter instance to the widget tree:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart'; // Import the Counter class

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterScreen(),
    );
  }
}

Step 3: Consume the Provided Value

Use the Consumer widget to listen to changes in the Counter instance and rebuild the UI accordingly:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart'; // Import the Counter class

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider Counter Example'),
      ),
      body: Center(
        child: Consumer<Counter>(
          builder: (context, counter, child) {
            return Text(
              'Count: ${counter.count}',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read<Counter>().increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

Practical Exercise

Exercise: Create a Todo App

Objective: Create a simple Todo app using the Provider package.

Steps:

  1. Create a Todo class with properties id, title, and isCompleted.
  2. Create a TodoList class that extends ChangeNotifier to manage a list of todos.
  3. Provide the TodoList instance to the widget tree using ChangeNotifierProvider.
  4. Create a screen to display the list of todos and a form to add new todos.
  5. Use Consumer to listen to changes in the TodoList and rebuild the UI.

Solution:

  1. Create the Todo class:
class Todo {
  final String id;
  final String title;
  bool isCompleted;

  Todo({
    required this.id,
    required this.title,
    this.isCompleted = false,
  });
}
  1. Create the TodoList class:
import 'package:flutter/foundation.dart';
import 'todo.dart'; // Import the Todo class

class TodoList with ChangeNotifier {
  List<Todo> _todos = [];

  List<Todo> get todos => _todos;

  void addTodo(Todo todo) {
    _todos.add(todo);
    notifyListeners();
  }

  void toggleTodoStatus(String id) {
    final todo = _todos.firstWhere((todo) => todo.id == id);
    todo.isCompleted = !todo.isCompleted;
    notifyListeners();
  }
}
  1. Provide the TodoList instance:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'todo_list.dart'; // Import the TodoList class

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => TodoList(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TodoScreen(),
    );
  }
}
  1. Create the TodoScreen:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'todo_list.dart'; // Import the TodoList class
import 'todo.dart'; // Import the Todo class

class TodoScreen extends StatelessWidget {
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider Todo Example'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              controller: _controller,
              decoration: InputDecoration(
                labelText: 'Add Todo',
                border: OutlineInputBorder(),
              ),
            ),
          ),
          ElevatedButton(
            onPressed: () {
              final todo = Todo(
                id: DateTime.now().toString(),
                title: _controller.text,
              );
              context.read<TodoList>().addTodo(todo);
              _controller.clear();
            },
            child: Text('Add'),
          ),
          Expanded(
            child: Consumer<TodoList>(
              builder: (context, todoList, child) {
                return ListView.builder(
                  itemCount: todoList.todos.length,
                  itemBuilder: (context, index) {
                    final todo = todoList.todos[index];
                    return ListTile(
                      title: Text(todo.title),
                      trailing: Checkbox(
                        value: todo.isCompleted,
                        onChanged: (value) {
                          todoList.toggleTodoStatus(todo.id);
                        },
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Summary

In this section, we learned about the Provider package and how it simplifies state management in Flutter applications. We covered:

  • The key concepts of Provider, ChangeNotifier, and Consumer.
  • How to set up and use the Provider package.
  • A practical example of a counter app.
  • An exercise to create a Todo app using Provider.

By mastering the Provider package, you can efficiently manage state in your Flutter applications, leading to cleaner and more maintainable code. In the next section, we will explore another state management solution: Riverpod.

Flutter Development Course

Module 1: Introduction to Flutter

Module 2: Dart Programming Basics

Module 3: Flutter Widgets

Module 4: State Management

Module 5: Navigation and Routing

Module 6: Networking and APIs

Module 7: Persistence and Storage

Module 8: Advanced Flutter Concepts

Module 9: Testing and Debugging

Module 10: Deployment and Maintenance

Module 11: Flutter for Web and Desktop

© Copyright 2024. All rights reserved