Flutter Bloc in E-commerce App

Andrey Poteryahin
8 min readFeb 9, 2020

--

The Open Flutter Project: E-commerce App uses Bloc architecture described here https://bloclibrary.dev/. If you are not familiar with Flutter Bloc you should go through examples available: Counter, Timer and others.

SQLite local database is used as a storage. The database structure is described in this article “Open Flutter Project: E-commerce App Local Database”.

This articles was created for new developers joining the Open Flutter Project: E-commerce App.

Folder and file structure

The App is broken down into a list of components: Home, Categories, Products, Cart, Checkout, Profile, Sign In / Sign Up, etc. Therefore each of them is represented by a separate folder and Bloc class that manages states and views of the component. A view is a separate screen of the app.

See the screenshot of the folder structure below.

You can find the overview of the app code below:

main.dart Launches the app, manages routing and SimpleBlocDelegate class implementation.

config/

  • theme.dart — App theme styles.
  • routes.dart — App routes

repos/

  • models/ — Folder with entity models described
  • product.dart

  • product_repository.dart — Product repository: get list of products and various attributes.

screens/ — Folder with all the app components.

products/

  • products_bloc.dart — Bloc implementation class
  • products_event.dart — Events within component.
  • products_state.dart — States of the component.
  • products_screen.dart — Manages list of views within components.
  • products.dart — Barrel file that exports all includes within component.

views/ — Folder with all the views within the component.

  • list_view.dart Product list view component.
  • wrapper.dart General class to establish standard navigation between views within component.

widgets/ — Folder with reusable widgets within the App.

  • scaffold.dart
  • bottom_menu.dart

main.dart

This file contains SimpleBlocDelegate class implementation take from here. It is used to manage all the transitions within the app.

The code that launches the app and does general routing. If you are already familiar to Flutter App development you can skip this part and move on.

config/routes.dart

General routes are described in config/routes.dart file.

Flutter Routes

config/theme.dart

Main sizes and colors are described as static fields in AppSizes and AppColors classes accordingly. They are further used in the ThemeData description and in some of the components.

Application Sizes and Colors

OpenFlutterEcommerceTheme class describes styles for most common elements within the app. Use lowerCamelCase style for identifiers within those classes according to Flutter and Dart Style.

Models

All models are located in repos/models/ folder. An example for product you can see below:

That is a basic model description that should be extended with copyWith and other useful methods in the future.

Repositories

Database wrappers, API calls and other data access utilities should be located in repos/ folder.

Home Screens

We have 3 variants of the home screen which are implemented as a separate view in screens/home/views/ folder. main1_view.dart, main2_view.dart, main3_view.dart — contain exact layout of all three variants.

Flutter Bloc Example

Let’s review the Bloc Example for the Product Screens we have.

We got here 3 repositories to access data: ProductRepository, CategoryRepository and HashtagRepository. We use them to get list of elements to populate in the UI. The data acquisition is done in mapEventToState method.

Flutter Bloc mapEventToState in Products list

Bloc States

We have our main state ProductsLoadedState that contains data about product list, hashtags and category. ProductInitialState is at Bloc initialization. ProductsErrorState is used in case we face an error during processing data.

Since ProductsLoadedState inherits ProductState which is Equatable we pass all the attributes into props to let our Bloc know when the state changes and when rendering of the views is necessary.

@overrideList<Object> get props => [data, isLoading, showSortBy, sortBy];

Bloc Event

There are two events that we trigger. Right now different events and states are used to explain the overall logic of the app. Events are not used to switch between views. Their main purpose is to cause the App change state.

ProductStartEvent is triggered when we get to products screen. Category ID is passed so we know what products to pull from database. The other two events ProductShowSortByEvent and ProductChangeSortByEvent are used to show short by dialog and to tell current state that we have changed sortBy that is managed in mapEventToState of ProductBloc described above.

View Wrapper

The OpenFlutterWrapperState is the class that helps our Screen Wrappers switch the views within the component (like products). changePage method is used to switch there and back between views and is passed to the View.

The Views

Each view is a statefull widget. You can see in the example below how Product List View is initialized. We pass changeView function from the Wrapper to switch to a different view.

General Bloc workflow

First, screen widget (product_screen.dart) is rendered (ProductsScreen) and we call ProductShowListEvent after initilizing bloc.

@overrideWidget build(BuildContext context) {  return SafeArea(    child: OpenFlutterScaffold(    background: null,    title: “Products”,    body: BlocProvider<ProductBloc>(    create: (context) {      return ProductBloc(        productRepository: ProductRepository(),        categoryRepository: CategoryRepository(),        hashtagRepository: HashtagRepository()      )..add(ProductStartEvent(
widget.categoryId)
);
}, child: ProductsWrapper()), bottomMenuIndex: 1, ));}

Our mapEventToState method in ProductBloc matches ProductStartEvent and yields ProductsLoadedState.

if ( event is ProductStartEvent){  data = getStateData(event.categoryId);  yield ProductsLoadedState(    isLoading: false,    showSortBy: false,    sortBy: SortBy.Popular,    data: data);}

getStateData get all the data for the state from the repositories so we can use this date to initialize ProductsLoadedState.

ProductStateData getStateData(int categoryId) {  ProductStateData data = ProductStateData();  data.products = productRepository.getProducts(categoryId);  data.hashtags = hashtagRepository.getHashtags();  data.category = categoryRepository.getCategoryDetails(categoryId);  return data;}

ProductWrapper opens first widget from the screen views which is ProductsListView. We pass changePage method to allow fast switch between views in the future.

@overrideWidget build(BuildContext context) {  return BlocBuilder<ProductBloc, ProductState>(    bloc: BlocProvider.of<ProductBloc>(context),    builder: (BuildContext context, ProductState state) {      return getPageView(<Widget>[        ProductsListView(changeView: changePage),        ProductsCardView(changeView: changePage),    ]);  });}

That is how we get product list screen displayed

Calling changes on the screen

Now we need to show sort by widget where we select sorting direction.

We add a new event ProductShowSortByEvent in product_events.dart. Then we add new bool parameter in the state that will tell if we show sort by widget or not. In ProductLoadedState we add showSortBy parameter and add it to overrides in that class and inherited classes.

ProductBloc mapEventToState method is updated to match those two events.

else if ( event is ProductChangeSortByEvent){  ProductsLoadedState state = this.state as ProductsLoadedState;  yield state.copyWith(sortBy: event.sortBy, showSortBy: false);}else if ( event is ProductShowSortByEvent) {  ProductsLoadedState state = this.state as ProductsLoadedState;  yield state.copyWith(showSortBy: true);}

We have two main views types (list view and card view) and call according states.

When we render ProductsListView in build method we use state parameter to show or hide sort by widget.

state.showSortBy?  OpenFlutterSortBy(    currentSortBy: state.sortBy,    onSelect: ( (SortBy newSortBy)=>{…})  ) : Container()

When user clicks sort by area in the filters we need to call event that will trigger appropriate changes in the state to show the sort by widget.

OpenFlutterProductFilter(  width: width,  height: 24,  productView: productView,  sortBy: state.sortBy,  onFilterClicked: (() => {}),  onChangeViewClicked: (() => { … }),  onSortClicked: ((SortBy sortBy) => {    bloc..add(ProductShowSortByEvent()),  }),),

Now we need to pass sort by value back into our screen. OpenFlutterSortBy widget has parameter onSelect which is a fuction with SortBy variable. This variable is used to bring back our the selection into the screen.

We need to use current version of bloc within context to call event telling us we changed the state parameter to recently selected sort by value. We initialize variable bloc in the build method of the screen.

final bloc = BlocProvider.of<ProductBloc>(context);

and use it in onSelect parameter to create method that will be called when user clicks on one of the variants of sort by.

state.showSortBy?  OpenFlutterSortBy(    currentSortBy: state.sortBy,    onSelect: ( (SortBy newSortBy)=>{    bloc..add(ProductChangeSortByEvent(newSortBy))  })  ) : Container()

Another example of the event-state management feature is when we switch product list view. That small button at the top right corner

When we call OpenFlutterProductFilter widget we pass a function to call event switching state and then call changeView to show next view in the list of screen views described above.

OpenFlutterProductFilter(  width: width,  height: 24,  productView: productView,  sortBy: state.sortBy,  onFilterClicked: (() => {}),  onChangeViewClicked: (() => {    widget.changeView(changeType: ViewChangeType.Forward)  }),  onSortClicked: ((SortBy sortBy) => {    bloc..add(ProductShowSortByEvent()),  }),),

We call parent widget method changeView with parameter changeType representing direction of the change within the list of views to proceed to a different view (list or card product view).

widget.changeView(changeType: ViewChangeType.Forward)

That is it. We are good to go to add more screens and interactive widgets in our app. If you have questions or comments ask.

Widgets Library

All the elements are broken down into widgets. The most commonly used on almost all the screens are OpenFlutterScaffold and OpenFlutterBottomMenu widgets which represent general layout for the screen and bottom navigation.

OpenFlutterScaffold

The parameters for that widget are:

background — background color

title — Top Bar Title

body — the screen widget

bottomMenuIndex — Index of selected bottom element (0–4).

tabBarList — list of tab bar elements (used on category list in list view)

tabController — TabController to manage the tab content on the screen.

The Scaffold widget:

OpenFlutterEcommerceBottomMenu widget

We pass only menuIndex parameter into BottomMenu widget to tell which menu item should be selected.

See complete list of Flutter widgets used in our project in “Open Flutter Project: E-commerce App Flutter Widgets”.

What’s next?

You can take a look at our github repo here.

Get in touch via LinkedIn to share your ideas and thoughts or to join our team to build the E-commerce Mobile App.

--

--