/shopping_cart

Powerful shopping cart package for Flutter

Primary LanguageDartBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

About ShoppingCart

ShoppingCart is a Flutter library that simplifies the creation and management of a shopping cart in your Flutter applications.

Installing

Add the following dependency to your pubspec.yaml file:

dependencies:
  shopping_cart: ^0.0.1

Then, import the library in your Dart code:

import 'package:food_cart/food_cart.dart';

Additional dependencies

I also recommend adding the following 2 packages, they are necessary for the full functioning of the library.

dependencies:
  get:
  equatable:
  • "Get" is an excellent state management tool. The ShoppingCart is made using it. You will need it if you want to make the values of the shopping cart observable (like items in cart screen or total cart price for example). But it's not mandatory, as I've added the ability to use Streams if you don't want to use Get.
  • The package "Equatable" is necessary to compare the models of your products in the cart and find the necessary one.

Usage

Creating custom item class

Firstly you need to create a model of your product, this is a mandatory step!

Simply create a class with any name and extend it from the ItemModel class, which is part of this package, and also, if you added the equatable package, add the EquatableMixin mixin.

import 'package:equatable/equatable.dart';
import 'package:food_cart/food_cart.dart';

class FoodModel extends ItemModel with EquatableMixin {
    // Create all the fields of the class 
    // that you need for your specific case.
  final String name;
  final String urlPhoto;
  String specialInstruction;

  FoodModel({
    required this.name,
    required this.urlPhoto,
    this.specialInstruction = '',

    // this field come from ItemModel class
    required super.id, 

    // This field come from ItemModel class
    required super.price,

    // This field come from ItemModel class
    super.quantity = 1,
  });
  

  // This line of code comes from equatable package, 
  // and it is required! In square brackets pass all fields
  // from your model, but don't pass 'id', 'price' and 'quantity'!!!
  @override
  List<Object?> get props => [ name, urlPhoto, specialInstruction]; 
}

The abstract class ItemModel already has 3 mandatory fields without which nothing will work:

  • Unique id (id)
  • Quantity of goods (quantity)
  • Cost of goods (price)

Therefore, please do not recreate these fields in your class!

Initialisation

After you have created your item model, it is necessary to initialize the shopping cart. This should be done before you start using the API of this package.

To get started, call the ShoppingCart.init<T>() method to initialize a new instance of Cart and register it. Replace with the type of item that you want to add to the cart. For example, to add FoodModel objects to the cart:

ShoppingCart.init<FoodModel>();

Get instance

One of the great advantages of my package is that you can get the cart instance anywhere in your code and you don't need to save and pass it from screen to screen on your own.

To retrieve the current instance of Cart call the ShoppingCart.getInstance() method. For example:

final cart = ShoppingCart.getInstance<FoodModel>();

Make sure to pass your item model class that you created at the beginning of this guide in <> brackets, in my case it's FoodModel. If you don't pass it, you will get an error!

Getters

CartItems

To get the list of items in the cart, simply access the cartItems getter.

final instance = ShoppingCart.getInstance<FoodModel>();

final items = instance.cartItems;

The cartItems object is already observable and has the type RxList<T> since I used Get package.

CartTotal

Returns the total price of all items in the cart.

final instance = ShoppingCart.getInstance<FoodModel>();

final cartTotal = instance.cartTotal;

This getter calculates the total price of all items in the cart by iterating through cartItems and adding the price of each item multiplied by its quantity. The result is returned as a double.

TotalCartPriceInt

This getter returns the total price of all items in the cart as an integer. This is simmilar to cartTotal, but it return int

final instance = ShoppingCart.getInstance<FoodModel>();

final totalCartPriceInt = instance.totalCartPriceInt;

itemCount

Returns the number of items currently in the cart.

final instance = ShoppingCart.getInstance<FoodModel>();

final cartLength = instance.itemCount;

This is same as instance.cartItems.length

cartItemsStream

A stream of cartItems that emits whenever the list of items in the cart changes.

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

  @override
  Widget build(BuildContext context) {

    // Get cart instance
    final cartInstance = ShoppingCart.getInstance<FoodModel>();

    // Stream will return List<YOUR ITEM MODEL>
    return StreamBuilder<List<FoodModel>>(

    // pass getter cartITemsStream
      stream: cartInstance.cartItemsStream,

      // Pass the cartItems as initialData
      // to the stream builder, otherwise, the stream builder will
      // initially return an empty list.
      initialData: cartInstance.cartItems,
      builder: (context, AsyncSnapshot<List<FoodModel>> snap) {

        // retrive data from snaphot and use it
        final List<FoodModel> items = snap.data!;
        
        return; /// return any widget you need
      },
    );
  }
}

Methods

Get item from cart

Returns the item from cartItems that matches the given itemModel.

final instance = ShoppingCart.getInstance<FoodModel>();

final item = instance.getItemFromCart(yourItemModel);

This method is mainly used for all other methods, such as increasing the quantity of an item, decreasing it, deleting it, updating it. But in some cases, you may also need it.

Finding an item by ID

To find an item in the cart by ID, call the findItemById method on the Cart instance:

final instance = ShoppingCart.getInstance<FoodModel>();

final item = instance.findItemById(1);

Has item with id

Returns true if an item with the specified id is already in the cart. Returns false otherwise.

final instance = ShoppingCart.getInstance<FoodModel>();

// return true if item with the specified `id` is already in the cart
// return false if item with the specified `id is is not added to the cart
// Throw ArgumentError if id <= 0
final bool result = instance.hasItemWithId(1);

Contains cart item

This method checks if the item is already in the cart.

This is like method getItemFromCart, but this method will return a bool value

final instance = ShoppingCart.getInstance<FoodModel>();

// Returns true if the [itemModel] is already in the cart.
// Returns false if not
final bool result = instance.containsCartItem(yourItemModel);

Adding items to the cart

To add an item to the cart, simply call the addItemToCart method on the Cart instance:

final instance = ShoppingCart.getInstance<FoodModel>();

// create your item model
final item = FoodModel(
  id: 1,
  name: 'Cheeseburger',
  price: 8.99,
  urlPhoto: 'https://example.org/Cheeseburger',
);

// add item to the cart
instance.addItemToCart(item);

If the item already exists in the cart, its quantity will be increased by one.

Removing items from the cart

To remove an item from the cart, call the removeItemFromCart method on the Cart instance:

final instance = ShoppingCart.getInstance<FoodModel>();

// Pass your item model as argument
// This method will find the right model in cart items and it will remove it
instance.removeItemFromCart(item); 

Increment item quantity

Increases the quantity of an item in the cart by one.

final instance = ShoppingCart.getInstance<FoodModel>();

// It will fint the right item in cartItem and it will
// increment quantity by 1.
instance.incrementItemQuantity(yourItem);

Decrement item quantity

Decrement the quantity of an item in the cart by one.

final instance = ShoppingCart.getInstance<FoodModel>();

// It will fint the right item in cartItem and it will
// decrement quantity by 1.
instance.decrementItemQuantity(yourItem);

if the quantity of item is equal to 1, it will remove your item from cart!

Calculate item total price by ID

Calculates the total price of an item with the specified id in the cart.

final instance = ShoppingCart.getInstance<FoodModel>();

// Returns the total price of the item, 
// which is the product of the item's price and quantity.
/// Throws an [ArgumentError] if the [id] is not valid.
final double totalPrice = instance.calculateItemTotalPriceById(1);

But you can also use the getter calculateTotalItemPrice from ItemModel. It will do the same.

For example

final instance = ShoppingCart.getInstance<FoodModel>();

// get your item instance
final item = instance.findItemById(1);

 /// The total price of the item calculated 
 /// by multiplying price with quantity.
final double totalItemPrice = item.calculateTotalItemPrice;

Update item

In some case you may need to update your item model values.

final instance = ShoppingCart.getInstance<FoodModel>();

instance.updateItemInCart(oldModel, updatedModel);

Throws a StateError if the old item is not found in the cart.

If the old and new items are the same and have the same quantity, no update is performed.

If the new item's quantity is 0, the old item will be removed from the cart.

The cartItems will be updated and refreshed after the item is updated or removed.

Parameters:

  • oldItem: The old item in the cart to be updated.
  • newItem: The new item to replace the old item in the cart.

The full example of using this method will be shown below.

Refresh cart

Use this method to update the UI after updating the values in your product model.

final instance = ShoppingCart.getInstance<FoodModel>();

instance.refreshCart();

But do not use this method with other methods of this package as they already use it by default.

Clear all items

To clear all items from the cart, call the clearCart method on the Cart instance:

final instance = ShoppingCart.getInstance<FoodModel>();

instance.clearCart();

Rebuild ui

Most likely, you will want to update (redraw) the ui, for example, when a new item is added to the cart, or when the total cost of all items in the cart is updated.

As I mentioned before, cartItems is observable and uses Get for this purpose. Below, I will show two ways to rebuild the ui when performing any manipulations with cartItems.

And for this, you won't need to use StatefulWidgets and SetState!

Use Get

For example, let's say we need to display the total price of items in the cart on the screen, but we also need the widget with the total price to be redrawn every time the user, for example, removes an item from the cart or changes its quantity.

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

  @override
  Widget build(BuildContext context) {

    // get cart instance
    final cartInstance = ShoppingCart.getInstance<FoodModel>();

    // get cart total
    final cartTotal = cartInstance.cartTotal;

    // text widget with cart total
    return Text('Cart total $cartTotal');
  }
}

If you will try to use it, you will see, that when you try for example remove one item from cart, your ui will not rebuild.

For make it rebuild you need to wrap your Text widget with Obx widget witch come from Get package and it will work fine

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

  @override
  Widget build(BuildContext context) {
    final cartInstance = ShoppingCart.getInstance<FoodModel>();
    final cartTotal = cartInstance.cartTotal;

    return Obx(
      () => Text('Cart total $cartTotal');
    );
  }
}

All you need to redraw your ui, is to wrap any getter of this package with the Obx widget, and then every time cartItems is updated, your UI will be redrawn! It's really easy!


Lets look for another example. For example we will display all items in cart in list and by long tap we will remove item from cart and it will rebuild ui automatically!

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

  @override
  Widget build(BuildContext context) {

    // get cart instance
    final instance = ShoppingCart.getInstance<FoodModel>();

    // Use Obx widget for make ui rebuild any time 
    // when cartItems is updated
    return Obx(
      () => ListView.builder(
        itemBuilder: (context, int index) {

          // get single item from cartItems by index
          final item = instance.cartItems[index];

          return ListTile(
            title: Text(item.name),
            leading: Image.network(item.urlPhoto),
            trailing: Text('${item.calculateTotalItemPrice}'),

            // remove item from cart onLongPressEvent
            // it will rebuild your ui automatically
            onLongPress: () => instance.removeItemFromCart(item),
          );
        },
        // pass cart items length by using getter `itemCount`
        itemCount: instance.itemCount,
      ),
    );
  }
}

About how Obx works, you can read on the Get package page.

You can check out a complete example of using this package by downloading the example project from Github repo and running it on your machine. Everything is already set up and working there. I tried to use all the features of this package in that project!


Use StreamBuilder

If for some reason you don't want to use Get, I have also created a stream that returns the cartItems.

Example of usage

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

  @override
  Widget build(BuildContext context) {
    // get cart instance
    final instance = ShoppingCart.getInstance<FoodModel>();

    return StreamBuilder(
      // pass cartItemsStream
      stream: instance.cartItemsStream,

      // pass cartItems as initialData, it will return the current state of cartItems.
      initialData: instance.cartItems,
      // snapshot will return you a List<YOUR ITEM MODEL>, in my case FoodModel
      builder: (context, AsyncSnapshot<List<FoodModel>> snap) {

        // get data from snapshot
        final List<FoodModel> data = snap.data!;

        // Next return any widget you want and ui will rebuild automatically 
        //when cartITems was updated
        return ListView.builder(
          itemBuilder: (context, int index) {
            // get single item from cartItems by index
            final item = data[index];

            return ListTile(
              title: Text(item.name),
              leading: Image.network(item.urlPhoto),
              trailing: Text('${item.calculateTotalItemPrice}'),

              // remove item from cart onLongPressEvent
              // it will rebuild your ui automatically
              onLongPress: () => instance.removeItemFromCart(item),
            );
          },
          itemCount: instance.itemCount,
        );
      },
    );
  }
}

Conclusion

Thank you for watching until the end! I hope I was able to explain how this package works. But if you still have any questions, you can write to me on Telegram or GitHub, and I will try to help as much as possible.

I created this package for myself, as I often develop applications with a shopping cart. This package speeds up my work, and I hope it will help you too.

Buy me a coffe