How do you pass data with provider in Flutter?

When we want to pass data with Provider in Flutter we need to be careful at the very beginning. Firstly, we cannot equate it passing data between screens.

Why?

Because we can pass data between screens without the latest Provider package, ChangeNotifierProvider, and ChangeNotifier.

Let’s consider a simple data model where we have a Book class and a list of dummy data based on that Book class constructors.

We’ve already created a simple data model to achieve that, and have a respective GitHub Repository, where you can check the code snippet.

Let us see a glimpse of Book class first.

class Book {
  final String id;
  final String title;
  final String description;
  final double price;
  final String imageUrl;
  bool isFavorite;

  Book({
    required this.id,
    required this.title,
    required this.description,
    required this.price,
    required this.imageUrl,
    this.isFavorite = false,
  });  
}

Now, based on that, we have some dummy data also so we can display the Book items on the books overview screen.

class BooksOverviewScreen extends StatelessWidget {
  BooksOverviewScreen({Key? key}) : super(key: key);

  final List<Book> books = [
    Book(
      id: 'p1',
      title: 'Beginning Flutter With Dart',
      description: 'You can learn Flutter as well Dart.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2014/09/05/18/32/old-books-436498_960_720.jpg',
    ),
    Book(
      id: 'p2',
      title: 'Flutter State Management',
      description: 'Everything you should know about Flutter State.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2016/09/10/17/18/book-1659717_960_720.jpg',
    ),
    Book(
      id: 'p3',
      title: 'WordPress Coding',
      description:
          'WordPress coding is not difficult, in fact it is interesting.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2015/11/19/21/10/glasses-1052010_960_720.jpg',
    ),
    Book(
      id: 'p4',
      title: 'PHP 8 Standard Library',
      description: 'PHP 8 Standard Library has made developers life easier.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2015/09/05/21/51/reading-925589_960_720.jpg',
    ),
    Book(
      id: 'p5',
      title: 'Better Flutter',
      description:
          'Learn all the necessary concepts of building a Flutter App.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2015/09/05/07/28/writing-923882_960_720.jpg',
    ),
    Book(
      id: 'p6',
      title: 'Discrete Mathematical Data Structures and Algorithm',
      description:
          'Discrete mathematical concepts are necessary to learn Data Structures and Algorithm.',
      price: 9.99,
      imageUrl:
          'https://cdn.pixabay.com/photo/2015/11/19/21/14/glasses-1052023_960_720.jpg',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('MyShop'),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10.0),
        itemCount: books.length,
        itemBuilder: (ctx, i) => BookItem(
          books[i].id,
          books[i].title,
          books[i].imageUrl,
        ),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3 / 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
      ),
    );
  }
}

The above code is quite simple. Because we pass data between classes through class constructor.

The following code snippet handles that passage of data.

itemBuilder: (ctx, i) => BookItem(
          books[i].id,
          books[i].title,
          books[i].imageUrl,
        ),

Now we can have a Book Item controller, where we can request that data through class constructor.

In addition, we can display the title, image etc.

import 'package:flutter/material.dart';

class BookItem extends StatelessWidget {
  final String id;
  final String title;
  final String imageUrl;

  BookItem(this.id, this.title, this.imageUrl);

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        child: GestureDetector(
          onTap: () {},
          child: Image.network(
            imageUrl,
            fit: BoxFit.cover,
          ),
        ),
        footer: GridTileBar(
          backgroundColor: Colors.black87,
          leading: IconButton(
            icon: const Icon(Icons.favorite),
            color: Theme.of(context).primaryColor,
            onPressed: () {},
          ),
          title: Text(
            title,
            textAlign: TextAlign.center,
          ),
          trailing: IconButton(
            icon: const Icon(
              Icons.shopping_cart,
            ),
            onPressed: () {},
            color: Theme.of(context).primaryColor,
          ),
        ),
      ),
    );
  }
}

However, the above data model represents the whole book items. Not a single book.

How we can achieve that?

Here Provider package, and ChangeNotifierProvider come to our rescue.

If we add Provider package ChangeNotifierProvider with our book class, we can easily notify the listener widget in our widget tree.

What is provider architecture in Flutter?

The main slogan of provider architecture in Flutter is, provide the dependencies to another widget. As a result, now, we can think of a single book now.

We also need to understand another key concept. In the books overview screen, we’ve already instantiated the Book object.

Consequently, we want only the book, or to be very specific here, the product ID only.

Therefore, first thing we need to do is, add the dependencies of Provider package to our “pubspec.yaml” file first.

Next, we change our Book class to this:

import 'package:flutter/foundation.dart';

class Book with ChangeNotifier {
  final String id;
  final String title;
  final String description;
  final double price;
  final String imageUrl;
  bool isFavorite;

  Book({
    required this.id,
    required this.title,
    required this.description,
    required this.price,
    required this.imageUrl,
    this.isFavorite = false,
  });
}

Why should we do that?

Because we want to provide single book object to Book item widget like this:

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

import '../views/book_detail_screen.dart';
import '../models/book.dart';

class BookItem extends StatelessWidget {
  const BookItem({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final product = Provider.of<Book>(context, listen: false);
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pushNamed(
              BookDetailScreen.routeName,
              arguments: product.id,
            );
          },
          child: Image.network(
            product.imageUrl,
            fit: BoxFit.cover,
          ),
        ),
        footer: GridTileBar(
          backgroundColor: Colors.black87,
          leading: Consumer<Book>(
            builder: (ctx, product, _) => IconButton(
              icon: Icon(
                product.isFavorite ? Icons.favorite : Icons.favorite_border,
              ),
              color: Theme.of(context).primaryColor,
              onPressed: () {
                product.toggleFavoriteStatus();
              },
            ),
          ),
          title: Text(
            product.title,
            textAlign: TextAlign.center,
          ),
          trailing: IconButton(
            icon: const Icon(
              Icons.shopping_cart,
            ),
            onPressed: () {},
            color: Theme.of(context).primaryColor,
          ),
        ),
      ),
    );
  }
}

In the above code, two lines are extremely important.

First, we have imported the Book data class to this widget. And, next, based on the mixin of ChangeNotifierProvider with Book class, now we can provide a single book, or product ID.

As we can see, we don’t pass data through class constructor anymore.

On the contrary, we got it through this line,

final product = Provider.of<Book>(context, listen: false);

And in the Books overview screen we also have not sent all books data through Book Item constructor. Instead, we’ve used another widget Book Grid as the body.

body: BooksGrid(showFavs: _showOnlyFavorites),

After that, in that Book Grid widget, we return a GridView builder like the following.

final productsData = Provider.of<Books>(context);
final products = productsData.items;
return GridView.builder(
      padding: const EdgeInsets.all(10.0),
      itemCount: products.length,
      itemBuilder: (ctx, i) => ChangeNotifierProvider.value(        
        value: products[i],
        child: const BookItem(),
      ),

Now as a ChangeNotifierProvider value we pass one product object from the instantiated books or products.

This part is little bit tricky, but based on that the Book Item widget now can provide one product object as an argument, while navigating to the books detail page.

child: GridTile(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pushNamed(
              BookDetailScreen.routeName,
              arguments: product.id,
            );
          },
          child: Image.network(
            product.imageUrl,
            fit: BoxFit.cover,
          ),
        ),

Since, we’ve building this Book shopping cart together, let us keep in touch with the further progress. Until now, you’ll get the full code snippet at this GitHub Repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter


Posted

in

, ,

by

Comments

3 responses to “How do you pass data with provider in Flutter?”

  1. […] value. And with the help of ChangeNotifierProvider value we have used the concept of Provider and ChangeNotifier to read existing data from the local […]

  2. […] can do that either using Provider data model and ChangeNotifierProvider or we can use Widget class […]

  3. […] Just for little recapitulation let us first remember what Provider is. Provider package in Flutter serves us in many purposes, that also includes passing a global style or data across the flutter app. We can pass the provided value through child constructors. […]

Leave a Reply