Flutter Course: Exploring the Provider Tool

Hello! In this course, we will take a deep dive into Provider, a popular tool for state management in Flutter. State management is a crucial element in the process of developing Flutter applications, as it is important to manage the flow of data and UI updates effectively. Provider is a tool that helps implement this state management easily.

1. What is Provider?

Provider is a library that assists in effectively managing and passing state within Flutter applications. It was created by the official Flutter team and is characterized by a simple API and high performance. Provider is based on InheritedWidget and has the ability to detect changes in data and automatically update the UI. It offers advantages such as simplification of state management, improved code reusability, and ease of testing.

1.1 Why should we use Provider?

  • Simple API: Provider implements state management in a simpler way with an easy-to-understand and use API.
  • Performance optimization: Only the UI widgets that subscribe to state changes are updated, ensuring excellent performance.
  • Dependency injection: Provider provides an easy way to inject dependencies.
  • Ease of testing: Using Provider makes it easier to change the application’s state for testing purposes.

2. Installing Provider

To use Provider, you must first install the library. Open the pubspec.yaml file of your Flutter project and add the following dependency:

dependencies:
  provider: ^6.0.0

After adding the dependency, run the following command to fetch the package:

flutter pub get

3. Basic Usage of Provider

To understand the basic usage of Provider, let’s look at a simple example. Here, we will implement a counter application.

3.1 Creating a model class

First, we need to create a model class to manage the state of the counter. Let’s create a class called Counter as follows:

import 'package:flutter/foundation.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Notify about state change
  }
}

3.2 Managing state with Provider

Now, let’s register the Counter class as the application state using Provider. We will modify the main.dart file to register Provider:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

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

3.3 Creating the UI

Now we will create the UI to complete the counter application. Let’s write the CounterScreen:

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of(context);
    
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Current Count:',
              style: TextStyle(fontSize: 24),
            ),
            Text(
              '${counter.count}',
              style: TextStyle(fontSize: 48),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

Now, when you run this application and press the ‘Add’ button, you can see the count increase. This demonstrates how to effectively manage data and update the UI using Provider.

4. Various uses of Provider

Provider can be utilized in various ways beyond basic usage. You can combine multiple Providers or use Nested Providers, allowing for flexible state management tailored to different scenarios.

4.1 Using MultiProvider

When there is a need to manage multiple states, you can use MultiProvider. For example, if you need to manage a counter and an additional state, you can write it as follows:

class Models {
  static Counter counter = Counter();
  static AnotherModel anotherModel = AnotherModel();
}

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => Models.counter),
        ChangeNotifierProvider(create: (context) => Models.anotherModel),
      ],
      child: MyApp(),
    ),
  );
}

4.2 Using the Consumer widget

When fetching and changing data in the UI, you can use the Consumer widget to selectively subscribe to only the data you need. Here is an example of using Consumer:

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

By using Consumer, we subscribe to changes in that state, and the UI within that block will only rebuild when updated.

5. Real-world example: Application in a complex environment

Now, let’s explore how to utilize Provider in a more complex application rather than a simple one. For example, we will create a shopping cart application to manage products purchased by users.

class CartItem {
  final String name;
  final double price;

  CartItem(this.name, this.price);
}

class Cart with ChangeNotifier {
  final List _items = [];

  List get items => _items;

  void addItem(CartItem item) {
    _items.add(item);
    notifyListeners();
  }

  double get totalAmount {
    return _items.fold(0.0, (total, item) => total + item.price);
  }
}

Now we will create a CartProvider and register it with MultiProvider so that it can be used across different parts of the app:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => Cart()),
      ],
      child: MyApp(),
    ),
  );
}

Next, let’s build the UI that allows users to add products. The user interface will include a product list and a cart button:

class ProductList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cart = Provider.of(context);
    
    return ListView(
      children: [
        ListTile(
          title: Text('Product 1'),
          trailing: IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
              cart.addItem(CartItem('Product 1', 29.99));
            },
          ),
        ),
        ListTile(
          title: Text('Product 2'),
          trailing: IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
              cart.addItem(CartItem('Product 2', 19.99));
            },
          ),
        ),
      ],
    );
  }
}

6. Advantages and disadvantages of state management

State management with Provider offers many advantages. However, there are also drawbacks. Without a deep understanding of state management, incorrect implementations can lead to code complexity.

6.1 Advantages

  • Reusable code: The same state can be easily used across multiple screens.
  • Performance improvement: Only the necessary widgets are updated, providing performance benefits.
  • Ease of maintenance: Thanks to a simple API, maintaining the code becomes easier.

6.2 Disadvantages

  • Complex applications can make the structure of Provider even more complicated.
  • If the scope of the state is unclear, it can be misused.
  • Developer experience is crucial for implementing proper state management.

7. Conclusion

Today, we explored the Provider tool in Flutter. Provider leverages the power of state management to make the flow of the application smoother. As the complexity of apps increases, the significance of state management also rises, and I hope this course has helped you grasp the basic concepts and usage of Provider.

In the next course, we will cover more advanced features and patterns. If you have any questions or need additional help regarding this course, please leave a comment. Thank you!