Manage state using provider package in a simple way

Pravin Palkonda
8 min readJan 11, 2023

--

State management is the most discussed topic while developing the application. When we work on any application, it is necessary to maintain the state of the application. Flutter has many packages that we can use to maintain the state of the application like Getx, Mobx, Bloc, Provider, etc. But which package is the best?

Each state management package has its advantages.No one can say which one is the best. They have their pros and cons.

I have mostly used the provider package for state management because it is easy to use and also it is recommended to start with the provider at a beginner level. Once you get hands-on with the provider package, you can try state management with other packages like Getx, BLoC, etc.

Let's understand the provider package with a demo application. Let's work on the application where users can add items to the cart and remove items from the cart using the provider package.

Firstly we need to add the dependency of the provider package.

Goto to pub.dev. Search for the provider package and check the latest version of the package and add it to the pubspec.yaml file.

Now create folders and name them screens, providers, and widgets. You can directly create files but it is good practice to separate your files into different folders.

Screens: In this folder, we include all our UI files.

Providers: In this folder, we include the providers' file

Widgets: Here we include all are custom widgets files

In this application, we are using a provider package. So we should more focus to use stateless widgets. If we use the Statefull widget, the build method is called again and again if there is any state gets changed (setState()).

Let's create a file for the home screen under the screen folder in which we display the list of items.

home_screen.dart


import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
);
}
}

Now in the main.dart file remove all the boilerplate code and wrap MaterialApp with the multi-provider. In the provider list, we have to add the provider file we have created, so the providers get initialized whenever the application gets started.


main.dart file

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/providers/item_data_provider.dart';
import 'package:provider_demo/screens/home_screen.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => ItemDataProvider(),
)
],
child: MaterialApp(
title: 'Flutter provider Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomeScreen(),
),
);
}
}

Now create a file and name it item_data_provider.dart under the provider's folder. In this file, we can write all business logic, create a list that is used the same in the application, etc. We have to extend the class with ChangeNotifier to make it a provider. Whatever the logic is written in this file, we can access it anywhere in the application by using the provider.


item_data_provider.dart

import 'package:flutter/material.dart';

class ItemDataProvider extends ChangeNotifier {
// List of all items
List<String> allItems = [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
"Item 9",
"Item 10",
];

}

Now home screen does not contain any list. Let's add the list using the listview builder. The list will contain the item name and add/remove button to add/remove items from the cart list.

Here we can use Consumer which is provided by the provider package, to consume a list of all items which is present in the item_data_provider.dart file.

home_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/providers/item_data_provider.dart';

class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Consumer<ItemDataProvider>(
builder: (context, itemDataProvider, child) {
return ListView.builder(
shrinkWrap: true,
itemCount: itemDataProvider.allItems.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
constraints:
const BoxConstraints(minHeight: 70, minWidth: 100),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.blue,
),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.65,
child: Text(
itemDataProvider.allItems[index],
textAlign: TextAlign.start,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
),
SizedBox(
height: 25,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(
color: Colors.white.withOpacity(0.4)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
),
onPressed: () {},
child: Text(
"Add",
style: TextStyle(color: Colors.white),
)),
)
],
),
),
));
}),
);
}
}

Now let’s create a common widget to display the item in the list and a common widget for the button. Creating a common widget has its advantages like it can be reused again and again. Also if any changes are there, we can change them in the common widget and it is reflected in the whole application.

Create a file and name it display_item.dart under the widgets folder.

dispay_item.dart


import 'package:flutter/material.dart';

class DisplayItem extends StatelessWidget {
// to pass a item name whenever we use DisplayItem Widget
final String? itemName;
const DisplayItem({super.key, this.itemName});

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
constraints: const BoxConstraints(minHeight: 70, minWidth: 100),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.blue,
),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.65,
child: Text(
itemName ?? "",
textAlign: TextAlign.start,
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
SizedBox(
height: 25,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.white.withOpacity(0.4)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
),
onPressed: () {},
child: const Text(
"Add",
style: TextStyle(color: Colors.white),
)),
)
],
),
),
);
}
}

Create a file and name it add_remove_button.dart

add_remove_button.dart

import 'package:flutter/material.dart';

class AddRemoveButton extends StatelessWidget {
final VoidCallback? onTap;
final String? buttonText;
const AddRemoveButton({super.key, this.onTap, this.buttonText});

@override
Widget build(BuildContext context) {
return SizedBox(
height: 25,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.white.withOpacity(0.4)),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
),
onPressed: onTap ?? () {},
child: Text(
buttonText ?? "",
style: TextStyle(color: Colors.white),
)),
);
;
}
}

Now let's write a logic to add items to the cart, remove items from the cart, and clear the cart in the item_data_provider.dart file.

item_data_provider.dart

import 'package:flutter/material.dart';

class ItemDataProvider extends ChangeNotifier {
List<String> cartList = [];
// List of all items
List<String> allItems = [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
"Item 9",
"Item 10",
];

// Getter which return the list if allItems
List<String> get getAllItemList => allItems;

// Getter which return the list of cartList
List<String> get getAllCartItemList => cartList;

/// to add item in cart
addItemToCart(var item) {
cartList.add(item);
notifyListeners(); /// to listen if there is any change
}

/// to remove item from cart
removeItemFromCart(var item) {
cartList.remove(item);
notifyListeners();
}

/// to clear cart list
clearCartList() {
cartList.clear();
notifyListeners();
}
}

Now we can call the addItemToCart method which requires a parameter as an item by using the provider. Same we can call removeItemFromCart to remove it from the cart list.

Now we have to show add/remove button in the item list. Here we can check that if the item is already present in the cart list then show the remove button else we can show add button.

display_item.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/providers/item_data_provider.dart';
import 'package:provider_demo/widgets/add_remove_button.dart';

class DisplayItem extends StatelessWidget {
// to pass a item name whenever we use DisplayItem Widget
final String? itemName;
const DisplayItem({
super.key,
this.itemName,
});

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
constraints: const BoxConstraints(minHeight: 70, minWidth: 100),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.blue,
),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.65,
child: Text(
itemName ?? "",
textAlign: TextAlign.start,
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
),
),

/// To show the remove button if item is already present in cart list
if (Provider.of<ItemDataProvider>(context, listen: false)
.cartList
.contains(itemName))
AddRemoveButton(
buttonText: "Remove",
onTap: () {
/// call method to remove item from cart by using provider
Provider.of<ItemDataProvider>(context, listen: false)
.removeItemFromCart(itemName);
},
),

/// To show the add button if item is not present in cart list
if (!Provider.of<ItemDataProvider>(context, listen: false)
.cartList
.contains(itemName))
AddRemoveButton(
buttonText: "Add",
onTap: () {
/// call method to add item to cart by using provider
Provider.of<ItemDataProvider>(context, listen: false)
.addItemToCart(itemName);
},
)
],
),
),
);
}
}

Now, we can use this DisplayItem widget on the home page.

home_screen.dart


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_demo/providers/item_data_provider.dart';
import 'package:provider_demo/widgets/display_item.dart';

class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home"),
),
body: Consumer<ItemDataProvider>(
builder: (context, itemDataProvider, child) {
return ListView.builder(
shrinkWrap: true,
itemCount: itemDataProvider.allItems.length,
itemBuilder: (context, index) => DisplayItem(
itemName: itemDataProvider.allItems[index],
));
}),
);
}
}

Now create a separate file for the cart screen. Here we will display a list of an item which is present in the cart list using consumer which is provided by the provider.

cart_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/item_data_provider.dart';
import '../widgets/display_item.dart';

class CartScreen extends StatelessWidget {
const CartScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Cart"),
),
body: Consumer<ItemDataProvider>(
builder: (context, itemDataProvider, child) {
return ListView.builder(
shrinkWrap: true,
itemCount: itemDataProvider.getAllCartItemList.length,
itemBuilder: (context, index) => DisplayItem(
itemName: itemDataProvider.getAllCartItemList[index],
));
}),
);
}
}

To move from the home screen to the cart screen, let’s display a cart icon in the app bar of the home screen. This cart icon is visible only if the cart list is not empty. Here we use the build context method to listen to the value of the cart item list.

home_screen.dart

appBar: AppBar(
title: const Text("Home"),
actions: [
if (context.watch<ItemDataProvider>().getAllCartItemList.isNotEmpty)

/// We can also use context.select which allows to listen only specific part of data

// if (context.select<ItemDataProvider, bool>(
// ((value) => value.getAllCartItemList.isNotEmpty)))
IconButton(
onPressed: () {
Navigator.push(context,
CupertinoPageRoute(builder: (_) => const CartScreen()));
},
icon: const Icon(Icons.shopping_cart))
],
),

So in this article, we have learned

We can consume any data by creating provider through Consumer.

We can use build context method to listen any value by using context.watch<ProviderName>

We can use build context method to listen only specific type of data by using context.select<ProviderName,bool>

Click here for the full source code.

Let me know in the comments if I need to correct anything. I will try to improve it.

Clap if you like the article. 👏

Go through my article which explains state management and api integration using flutter bloc.

--

--