How do you use ChangeNotifierProvider in Flutter?

What is the best ChangeNotifierProvider example in Flutter? How should we use ChangeNotifierProvider in Flutter?

What should be the correct approach to use ChangeNotifierProvider in Flutter?

Firstly, we cannot think of ChangeNotifierProvider without the provider package. And we know that provider pattern actually provides the current data model to the place where we need it.

In the previous article on provider data model, we’ve discussed the same topic. In the same vein, we have also seen that we can get the value of existing data quite easily using provider architecture.

Moreover, it’s always better than using class constructor.

In this article, we’ll see how we can retrieve existing value of a shopping app using ChangeNotifierProvider.

To do that, we must have a data model ready and instantiate the product objects first.

import 'package:flutter/foundation.dart';

class Product with ChangeNotifier {
  final String id;
  final String title;
  final String description;
  final double price;
  final String imageUrl;

  Product({
    required this.id,
    required this.title,
    required this.description,
    required this.price,
    required this.imageUrl,
  });
}

Next, we need to instantiate the product objects in a separate class Products. So that ChangeNotifierProvider can use value method to get the existing value.

import 'package:flutter/material.dart';

import 'product.dart';

class Products with ChangeNotifier {
  final List<Product> products = [
    Product(
      id: 'p1',
      title: 'Classic Watch',
      description: 'A Classic Watch accessorized with style.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2018/02/24/20/39/clock-3179167_960_720.jpg',
    ),
    Product(
      id: 'p1',
      title: 'Shoe with Gears',
      description: 'Shoes paired with exercise accessories.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2017/07/02/19/24/dumbbells-2465478_960_720.jpg',
    ),
...
// code is incomplete for brevity; please stay updated with this GitHub Repository

Now, as a rule, we’ll keep the ChangeNotifierProvider create named parameter points to the Products class. So that later we can use that type.

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => Products(),
        ),
      ],
      child: const ShopAppWithProvider(),
    ),
  );
}

We have used the default constructor because to create a value we should always use the default constructor. According to the documentation, we cannot create the instance inside build method.

It will lead to memory leaks and potentially undesired side-effects.

Where is ChangeNotifierProvider used?

We use ChangeNotifierProvider here for showing existing data model. Therefore, the Shop App With Provider stateless widget will show the products overview screen.

Or, we may consider it as the home page.

A Flutter Shop App homepage with Provider
A Flutter Shop App homepage with Provider

Actually, we’ve already instantiated the product object. Therefore, we need the object individually. That means, if we can get the product ID, our job is done.

Based on that ID, we can display all the products in a scrollable Grid. Each product ID points to the respective image and title.

Now, with the help of ChangeNotifierProvider, we can use three ways to display them.

Let’s see the first one’s code snippet.

class ShopAppWithProvider extends StatelessWidget {
  const ShopAppWithProvider({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: ShopHomePage(),
    );
  }
}

class ShopHomePage extends StatelessWidget {
  const ShopHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final products = context.watch<Products>().products;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products App'),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10.0),
        itemCount: products.length,
        itemBuilder: (ctx, i) => ClipRRect(
          borderRadius: BorderRadius.circular(10),
          child: GridTile(
            child: GestureDetector(
              onTap: () {},
              child: Image.network(
                context.watch<Products>().products[i].imageUrl,
                fit: BoxFit.cover,
              ),
            ),
            footer: GridTileBar(
              backgroundColor: Colors.black87,
              title: Text(
                context.watch<Products>().products[i].title,
                textAlign: TextAlign.center,
              ),
            ),
          ),
        ),
        // ),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
      ),
    );
  }
}

In the above code, h this line is extremely important.

final products = context.watch<Products>().products;

The next line, which plays a key role is the following one:

 itemBuilder: (ctx, i) => ClipRRect(
...
// code is incomplete for brevity

In the above code we pass the context and the index.

Now depending on that logic, we can change the above home page code this way now:

class ShopHomePage extends StatelessWidget {
  const ShopHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final product = Provider.of<Products>(context).products;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products App'),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10.0),
        itemCount: product.length,
        itemBuilder: (ctx, i) => ClipRRect(
          borderRadius: BorderRadius.circular(10),
          child: GridTile(
            child: GestureDetector(
              onTap: () {},
              child: Image.network(
                product[i].imageUrl,
                fit: BoxFit.cover,
              ),
            ),
            footer: GridTileBar(
              backgroundColor: Colors.black87,
              title: Text(
                product[i].title,
                textAlign: TextAlign.center,
              ),
            ),
          ),
        ),
        // ),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
      ),
    );
  }
}

In the above code, the following code is important.

final product = Provider.of<Products>(context).products;

We have used Provider of context method where we’ve mentioned the type. And then get all the products.

Further, we can also use ChangeNotifierProvider value and change the above code.

class ShopHomePage extends StatelessWidget {
  const ShopHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final product = Provider.of<Products>(context).products;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products App'),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10.0),
        itemCount: product.length,
        itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
          value: product[i],
          child: ClipRRect(
            borderRadius: BorderRadius.circular(10),
            child: GridTile(
              child: GestureDetector(
                onTap: () {},
                child: Image.network(
                  product[i].imageUrl,
                  fit: BoxFit.cover,
                ),
              ),
              footer: GridTileBar(
                backgroundColor: Colors.black87,
                title: Text(
                  product[i].title,
                  textAlign: TextAlign.center,
                ),
              ),
            ),
          ),
        ),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
      ),
    );
  }
}

All these three code snippet of products home page will display the products in the same way.

Furthermore, we can use the Gesture Detector on tap method to pass the product ID as an argument. As a result, we can display the product detail in a separate screen.

In the next article we will discuss that. So stay tuned to get updated on building a flutter shopping app.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

3 responses to “How do you use ChangeNotifierProvider in Flutter?”

  1. […] a result we can use all Provider methods and, above all, ChangeNotifierProvider […]

  2. […] we should keep the Provider above the root folder and use multi provider and ChangeNotifierProvider, so that we can later use other Providers as […]

  3. […] Finally, we will not use the multi provider technique. Instead we will use the ChangeNotifierProvider class. […]

Leave a Reply