Flutter Redux: Shopping Cart App From Scratch
Github Repository:
What will I learn?
- You learn how to make shopping cart app using flutter redux.
- You learn redux
Requirements
System Requirements:
- IDEA intellij, Visual Studio Code with the Dart/Flutter Plugins, Android Studio or Xcode
- The Flutter SDK on the latest Master Build
- An Android or iOS Emulator or device for testing
OS Support for Flutter:
- Windows 7 SP1 or later (64-bit)
- mac OS (64-bit)
- Linux (64-bit)
Required Knowledge
- A little understanding of key/value stores
- A fair understanding of Mobile development and Imperative or Object Oriented Programming
- Some understanding of real time databases
Resources for Flutter and this Project:
- Flutter Website: https://flutter.io/
- Flutter Official Documentation: https://flutter.io/docs/
- Flutter Installation Information: https://flutter.io/get-started/install/
- Flutter GitHub repository: https://github.com/flutter/flutter
- Flutter Dart 2 Preview Information: https://github.com/flutter/flutter/wiki/Trying-the-preview-of-Dart-2-in-Flutter
- Flutter Technical Overview: https://flutter.io/technical-overview/
- Dart Website: https://www.dartlang.org/
Difficulty
- Intermediate
What is Flutter?
Flutter is an SDK owned by Google to create applications for Android and iOS using a single codebase. Flutter uses the Dart programming language (also owned by Google). Flutter was in beta when I released this video, so things might still change a bit. But the SDK is already mature enough to write some cool production apps!
What is Redux?
Redux is an application design, made initially for JavaScript and now utilized as a part of uses worked with receptive systems, (for example, React Native or Flutter). Redux is rearranged rendition of Flux engineering, made by Facebook. Yet, what's about with Redux? Fundamentally, you have to know three things:
- there's a solitary wellspring of truth — your entire application state is kept just in one place (called store)
- the state is perused only — to change the application state you have to dispatch an activity, and after that new state is made
- changes are made with unadulterated functions — a unadulterated capacity (to improve, it's a capacity without symptoms) takes the past state and an activity, and returns new state
Sounds cool, however what are the upsides of that arrangement?
- we have control over the state — it implies that we precisely comprehend what caused state change, we don't have copied state and we can without much of a stretch take after information stream
- unadulterated reducer capacities are anything but difficult to test we can pass state, activity and test if result is right
- application is unmistakably structured — we have diverse layers for activities, models, business rationale, etc. — so you precisely know where to put another new element
- it's extraordinary design for more convoluted apps — you don't have to pass state down the entire view tree from parent to kid.
Redux Widgets on a basic illustration
The greater part of the above guidelines influences information to stream in Redux unidirectional. Be that as it may, what does it mean? Practically speaking it's altogether finished with activities, reducers, store and states. How about we envision application that shows catch counter:
- Your application has some state toward the starting (number of snaps, which is 0)
- In view of that state see is rendered.
- On the off chance that client taps on catch, there's activity send (e.g. IncrementCounter)
- Activity is gotten by reducer, which knows past state (counter 0), gets activity (Increment Counter) and can return new state (counter 1)
- Your application has new state (counter 1)
- In light of new state, see is rendered once more
So as should be obvious, by and large it's about the state. You have single application express, the state is perused just for see, and to make new state you have to send activity. Sending activity fires reducer that makes and transmits new application state. Also, history rehashes itself.
Redux Data Flow
Case of Shopping List App with Redux
Give me a chance to demonstrate how Redux functions practically speaking on more advances illustration. We'll make a basic Shopping Cart application. In this application there will be functionalities for:
- adding items
- marking items as checked
- and that’s basically all
Whole application code on GitHub:
Setup
To run with Redux on Flutter, you need to add dependencies to your pubspec.yaml
file:
flutter_redux: "^0.5.0"
You can check the newest version on flutter_redux package page.
Model
Our application needs to manage adding and changing items, so we‘ll use simple CartItem model to store single item state. Our whole application state will be just list of CartItems. As you can see, CartItem is just a plain Dart object.
import 'package:meta/meta.dart';class CartItem { String name; bool checked; CartItem({@required this.name, @required this.checked}); @override String toString() { return "$name : $checked"; } }
Actions
Firstly, need to declare actions. Action is basically any intent that can be invoked to change application state. In application we’ll have two actions, for adding and changing item:
import 'package:flutter_shoppingcart/model/cart_item.dart'; class AddItemAction { final CartItem item; AddItemAction(this.item);} class ToggleStateItemAction { final CartItem item; ToggleStateItemAction(this.item);} class DeleteItemAction { final CartItem item; DeleteItemAction(this.item);}
Reducing
At that point, we have to tell our application what ought to be finished with those activities. This is the reason reducers are for — they essentially take current application state and the activity, at that point they make and return new application state. We'll have two reducers strategies:
import 'package:flutter_shoppingcart/model/cart_item.dart';import 'package:flutter_shoppingcart/redux/action.dart'; ListCartItem cartItemsReducer(ListCartItem items, dynamic action) { if (action is AddItemAction) { return addItem(items, action); } else if (action is ToggleStateItemAction) { return toggleState(items, action); } else if (action is DeleteItemAction) { return deleteItem(items, action); } return items;} ListCartItem addItem(ListCartItem. items, AddItemAction action) { return new List.from(items)..add(action.item) List CartItem deleteItem(List CartItem items, DeleteItemAction action) { return new List.from(items)..remove(action.item);} ListCartItem toggleState(ListCartItem items, ToggleStateItemAction action) { ListCartItem newItems = items .map((item) = item.name == action.item.name ? action.item : item) .toList(); return newItems;}
Method appReducers()
delegates the action to proper methods. Both methods addItem()
and toggleItemState()
return new lists — that’s our new application state. As you can see, you shouldn’t modify current list. Instead of it, we create new lists every time.
Store Provider
Presently, when we have activities and reducers, we have to give place to putting away application state. It's called store in Redux and it's single wellspring of truth for our application.
import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart';import 'package:flutter_shoppingcart/model/cart_item.dart';import 'package:flutter_shoppingcart/redux/reducers.dart';import 'package:flutter_shoppingcart/shooping_cart.dart';import 'package:redux_dev_tools/redux_dev_tools.dart'; void main() { final store = new DevToolsStore ListCartItem (cartItemsReducer, initialState: new List()); runApp(new ShoppingApp(store));} class ShoppingApp extends StatelessWidget { final DevToolsStore ListCartItem store ShoppingApp(this.store); @override Widget build(BuildContext context) { return new StoreProvider( store: store, child: new MaterialApp( debugShowCheckedModeBanner: false, theme: new ThemeData.dark(), home: new ShoppingCart(store: store), ), ); }}Flutter
To create store, we need to pass reducers methods and initial application state. If we created the store, we must pass it to the StoreProvider to tell our application than it can be used by anyone who wants to request app state:
In the above example ShoppingCartApp()
is main application widget.
StoreConnector
Currently we have everything except… actual adding and changing items. How to do that? To make it possible, we need to use StoreConnector. It’s a way to get the store and make some action with it or read it’s state.
Firstly, we’d like to read current data and show this in a list:
import 'package:flutter/material.dart';import 'package:flutter_redux/flutter_redux.dart';import 'package:flutter_shoppingcart/model/cart_item.dart';import 'package:flutter_shoppingcart/shopping_item.dart'; class ShoppingList extends StatelessWidget { @override Widget build(BuildContext context) { return new StoreConnector ListCartItem, List CartItem ( converter: (store) = store.state, builder: (context, list) = new ListView.builder( itemCount: list.length, itemBuilder: (context, i) = new ShoppingItem(item: list[i]), ), ); }}
Code above wraps default ListView.builder
with StoreConnector. StoreConnector
can take current app state (which is ListCartItem
) and map this with converter function to anything. For purposes of this case, it’ll be the same state (ListCartItem
), because we need the whole list here.
Next, in builder function we get list — which is basically list of CartItems from store, which we can use for building ListView.
we have reading data here. Now how to set some data?
To do it, we’ll use also StoreConnector, but in a slightly different way.
lib/add_item_dialog.dart
import 'package:flutter/material.dart';import 'package:flutter_redux/flutter_redux.dart';import 'package:flutter_shoppingcart/model/cart_item.dart';import 'package:flutter_shoppingcart/redux/action.dart'; class AddItemDialog extends StatefulWidget { @override AddItemDialogState createState() { return new AddItemDialogState(); }}class AddItemDialogState extends State AddItemDialog { String itemName; @override Widget build(BuildContext context) { return new StoreConnector List CartItem, OnItemAddedCallback ( converter: (store) = (itemName) = store .dispatch(AddItemAction(CartItem(name: itemName, checked: false))), builder: (context, callback) = new AlertDialog( title: Text('Add Item'), contentPadding: const EdgeInsets.all(16.0), content: new Row( children: Widget[ new Expanded( child: new TextField( autofocus: true, decoration: new InputDecoration( labelText: "Item Name", hintText: "Eg. Apple"), onChanged: (text) { setState(() { itemName = text; }); }, )) ], ), actions: <Widget [ new FlatButton( onPressed: () { Navigator.pop(context); }, child: new Text("Cancel")), new FlatButton( onPressed: () { callback(itemName); Navigator.pop(context); }, child: new Text("Add")) ], ), ); }} typedef OnItemAddedCallback = Function(String itemName);
We used StoreConnector, as in the previous example, but this time, instead of mapping list of CartItems, into the same list, we’ll map this into OnItemAddedCallback. This way we can pass callback to the AddItemDialogWidget and call it when user adds some new item:
Now, every time user press “ADD” button, the callback will dispatch AddItemAction() event.
Now we can do very similar thing for toggling item state:
import 'package:flutter/material.dart';import 'package:flutter_redux/flutter_redux.dart';import 'package:flutter_shoppingcart/model/cart_item.dart';import 'package:flutter_shoppingcart/redux/action.dart'; class ShoppingItem extends StatefulWidget { final CartItem item; const ShoppingItem({Key key, this.item}) : super(key: key); @override ShoppingItemState createState() { return new ShoppingItemState(); }} class ShoppingItemState extends State ShoppingItem { @override Widget build(BuildContext context) { return new StoreConnector List CartItem, OnItemDeleted ( converter: (store) = (itemName) { store.dispatch(DeleteItemAction(widget.item)); }, builder: (context, callback) = new Dismissible( onDismissed: (_) { setState(() { callback(widget.item.name); }); }, child: new StoreConnector List CartItem, OnToggleStateAction( converter: (store) = (item) = store.dispatch(ToggleStateItemAction(item)), builder: (context, callback) = new ListTile( title: new Text(widget.item.name), leading: new Checkbox( value: widget.item.checked, onChanged: (newValue) { setState(() { callback(CartItem( name: widget.item.name, checked: newValue)); }); }), ), ), key: new Key(widget.item.name), ), ); }} typedef OnToggleStateAction = Function(CartItem item);typedef OnItemDeleted = Function(String itemName);
As in the previous example, we use StoreConnector for mapping ListCartItem into OnStateChanged callback. Now every time checkbox is changed (in onChanged method), the callback fires ToggleItemStateAction event.
Summary
In this tutorial, I have created flutter shopping cart application from scratch with functionality like add item, check item and delete them using REDUX architecture.
Proof of Work Done
You can also enjoy this tutorial here
Thank you for your contribution.
Please see already approved tutorials, such as this one link.
Looking forward to your upcoming tutorials.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]