Today we will build the UI for a multipurpose shop. This could be used for all type of shops. Here we are showcasing a food delivery app.

Download the project source code: Github

Keep me posting by donating: https://www.buymeacoffee.com/bi3cp0Zk5

Home Screen:

After setting up your project head to your home screen dart file and start coding.

The home screen Scaffold takes a bottomNavigationBar and an appBar as follows:

bottomNavigationBar: BottomNavigationBar( unselectedItemColor: Colors.grey, selectedItemColor: Colors.blueAccent, items: [ BottomNavigationBarItem( icon: Icon(Icons.title), title: Text("Home"), ), BottomNavigationBarItem( icon: Icon(Icons.card_giftcard), title: Text("Gift"), ), BottomNavigationBarItem( icon: Icon(Icons.favorite_border), title: Text("Favorite"), ), BottomNavigationBarItem( icon: Icon(Icons.person_outline), title: Text("Person"), ), ], ), appBar: AppBar( iconTheme: IconThemeData(color: Colors.black54), backgroundColor: Color(0xfff9f9f9), elevation: 0.0, leading: IconButton( icon: Icon( Icons.menu, ), onPressed: () {}, ), actions: [ IconButton( icon: Icon(Icons.shopping_basket), onPressed: () {}, ), ], ),

Let’s not forget to set the Scaffold color to a grayish color like this:

backgroundColor: Color(0xfff9f9f9),

Now the body, start with a SignleChildScrollView and give it some padding and a column as a child.

First two children are Text widgets.

Feel free to change the text to whatever you like, for example Tech Delivery or maybe Children Toys.

Because this is a multipurpose shop app you can use it to sell anything!

Make sure to give your client a way to search through your product. For that create a TextField as in this code:

TextField( decoration: InputDecoration( fillColor: Colors.white, filled: true, border: InputBorder.none, prefixIcon: Icon(Icons.search), hintText: "Search", ), ),

Because we are showing all the products in one page and there is no Category Screen.

Create a widget that allows the user to select which category is he/she most interested in.

Start by creating a Container and giving it a specific height, for example 81.

Then use this widget as a child.

import 'package:flutter/material.dart'; import 'package:shop_delivery/global.dart'; class CategoryContainer extends StatefulWidget { const CategoryContainer({ Key key, }) : super(key: key); @override _CategoryContainerState createState() => _CategoryContainerState(); } class _CategoryContainerState extends State { int _active = 0; @override Widget build(BuildContext context) { return ListView.builder( scrollDirection: Axis.horizontal, itemCount: cats.length, itemBuilder: (ctx, i) { return GestureDetector( onTap: () { setState(() { _active = i; }); }, child: Container( constraints: BoxConstraints(minWidth: 121), margin: EdgeInsets.only(right: i == cats.length - 1 ? 0 : 15.0), decoration: BoxDecoration( color: _active == i ? Color(0xff343c45) : Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( cats[i].icon, color: _active == i ? Colors.white : Colors.black, ), Text( "${cats[i].title}", style: Theme.of(context).textTheme.button.apply( color: _active == i ? Colors.white : Colors.black, ), ), ], ), ), ); }, ); } }

Once the user clicks on a specific category the Text Widget bellow changes to that specific choice.

(Even though here it’s not linked, you may consider using a Provider. Or any State Management approach to achieve that result)

Text( "Hamburger", style: Theme.of(context).textTheme.title.apply( fontWeightDelta: 2, ), ),

Now to the products list. Originally I wanted to use the Staggered Grid View widget but I finally decided to stick with a simple GridView Builder as you can see:

GridView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 15, mainAxisSpacing: 15, childAspectRatio: .7 ), itemCount: productsList.length, itemBuilder: (BuildContext context, int index) { return ProductContainer(id: index); }, )

This returns a ProductContainer widget while giving it the product index, and since the ProductContainer widget also has access to the list of products it also has the ability to pull the product information from that same list. Take a look:

import 'package:flutter/material.dart'; import 'package:shop_delivery/global.dart'; import 'package:shop_delivery/ui/screens/details.dart'; class ProductContainer extends StatelessWidget { final int id; const ProductContainer({Key key, this.id}) : super(key: key); @override Widget build(BuildContext context) { return GestureDetector( onTap: () => Navigator.push( context, MaterialPageRoute(builder: (context) => DetailsScreen(id: id)), ), child: Container( decoration: BoxDecoration( color: Colors.yellow, borderRadius: BorderRadius.circular(15.0), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text( "${productsList[id].price}", style: Theme.of(context).textTheme.title.copyWith( color: Colors.white, ), ), ), SizedBox( height: 15.0, ), Expanded( child: Align( alignment: Alignment.center, child: Hero( tag: '$id', child: Image.network( "${productsList[id].img}", fit: BoxFit.cover, // width: double.infinity, ), ), ), ), SizedBox( height: 15.0, ), Container( alignment: Alignment.center, padding: const EdgeInsets.all(15.0), width: double.infinity, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.all( Radius.circular(9.0), ), ), child: Text( "${productsList[id].title}", style: Theme.of(context) .textTheme .subtitle .copyWith(color: Colors.white), ), ) ], ), ), ); } }

Product Details Screen:

Now let’s get to the product details screen.

First set your Scaffold color to yellow. Then create your scaffolds body starting with the main column.

This column actually has two children, both expanded and both taking half of the available screen height.

The first child is a Stack which has a top positioned Row with two IconButtons, this serves as an app bar.

The second child of the stack is a position filled container which takes the product image.

Wrap is inside a Hero if you want the transition animation to occur.

Note that it is important to set your container alignment in order to be able to set the image width.

Expanded( child: Stack( children: [ Positioned( top: 0, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: Icon( Icons.chevron_left, ), onPressed: () => Navigator.pop(context), ), IconButton( icon: Icon( Icons.shopping_basket, ), onPressed: () => Navigator.pushNamed(context, 'orderscreen')), ], ), ), Positioned.fill( child: Container( width: double.infinity, alignment: Alignment.center, child: Hero( tag: '$id', child: Image.network( "${productsList[id].img}", width: MediaQuery.of(context).size.width * .7, ), ), ), ) ], ), ),

Now the bottom part, inside another Expanded widget create a white container with rounded top left and top right corners.

Give it a padding and a Column as a child.

Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(25.0), topRight: Radius.circular(25.0), ), ), padding: const EdgeInsets.all(15.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${productsList[id].title}", style: Theme.of(context).textTheme.display1, ), IconButton( icon: Icon( Icons.favorite_border, color: Colors.red, ), onPressed: () {}, ), ], ), Text( "Description", style: Theme.of(context).textTheme.title, ), Text( "${productsList[id].description}", ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Counter(), Text( "${productsList[id].price}", style: Theme.of(context).textTheme.title, ), ], ), Container( width: double.infinity, child: RaisedButton( child: Text( "Add To Cart", style: Theme.of(context).textTheme.button.apply( color: Colors.white, ), ), onPressed: () {}, color: Colors.green, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), ), ) ], ), ),

The column children are mostly Text widgets, but also a Counter widget that you have to create.

import 'package:flutter/material.dart'; class Counter extends StatefulWidget { @override _CounterState createState() => _CounterState(); } class _CounterState extends State { int _count = 1; @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( onTap: () { setState(() { _count += 1; }); }, child: Container( padding: const EdgeInsets.all(3.0), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(), ), child: Icon(Icons.add), ), ), SizedBox(width: 15.0), Text("$_count"), SizedBox(width: 15.0), GestureDetector( onTap: () { setState(() { _count -= 1; }); }, child: Container( padding: const EdgeInsets.all(3.0), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(), ), child: Icon(Icons.remove), ), ), ], ); } }

Well, sure this isn’t the most complete widget ever but for demonstration purposes it actually does the trick. But please feel free to improve it if you intend to use it for your own project.

Orders Screen:

In this screen you can use the AppBar.

Change it’s backgroundColor to Colors.white and it’s elevation to 0 to achieve the same look as in the design.

Now to divide the screen into two, use two Expanded widgets.

After the Text widget that displays the title of the page i.e. “My Order”, add a SizedBox as a separation between the title and the Orders list.

Inside an Expanded widget add a ListView.

Generate it’s children using a List.generate function that will return ListTiles with the product image as well as the price and the counter.

At the end of the order’s list add the delivery cost tile.

Here is how I did it:

Expanded( child: ListView( children: [ ...List.generate(productsList.length, (id) { return ListTile( leading: ClipRRect( borderRadius: BorderRadius.circular(5.0), child: Image.network( "${productsList[id].img}", width: 70, height: 100, fit: BoxFit.fill, ), ), title: Text( "${productsList[id].title}", style: Theme.of(context).textTheme.title, ), subtitle: Padding( padding: const EdgeInsets.symmetric( vertical: 15.0, ), child: Counter(), ), trailing: Text( "${productsList[id].price}", style: Theme.of(context).textTheme.title, ), ); }).toList(), Padding( padding: const EdgeInsets.only(top: 15.0), child: ListTile( leading: Container( width: 70, height: double.infinity, // margin: EdgeInsets.only(top: 15), decoration: BoxDecoration( color: Colors.cyan, borderRadius: BorderRadius.circular(5.0), ), child: Icon(Icons.dashboard), ), title: Text( "Delivery", style: Theme.of(context).textTheme.headline.apply( fontWeightDelta: 2, ), ), trailing: Text( "\$5.99", style: Theme.of(context).textTheme.title, ), ), ) ], ), ),

Now the second expanded child is a grey container with nice padding, which takes a Column as a child.

Again use a ListView to display the debit/credit card or payment methods that the user add.

Make sure to give him an option to add another payment methods if he wants to.

Right after that, add the “Confirm Payment” button and you are all set.

Expanded( child: Container( padding: EdgeInsets.all(15.0), color: Colors.grey[100], child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Payment", style: Theme.of(context).textTheme.title, ), Expanded( child: Container( padding: const EdgeInsets.symmetric(vertical: 15.0), child: ListView( scrollDirection: Axis.horizontal, shrinkWrap: true, children: [ ...List.generate(1, (i) { return Container( padding: const EdgeInsets.all(15.0), margin: const EdgeInsets.only(right: 15), alignment: Alignment.topLeft, width: MediaQuery.of(context).size.width / 3, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(5.0), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "**** 4832", style: Theme.of(context) .textTheme .title .apply(color: Colors.white), ), Spacer(), Text( "\$25.99", style: Theme.of(context).textTheme.title.apply( color: Colors.white, ), ), Image.asset("assets/img/mastercard.png"), ], ), ); }), InkWell( onTap: () {}, child: Container( margin: const EdgeInsets.symmetric( vertical: 15.0, ), alignment: Alignment.center, width: MediaQuery.of(context).size.width / 3, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5)), child: Icon(Icons.add), ), ) ], ), ), ), Container( height: 50, width: double.infinity, child: RaisedButton( child: Text( "Confirm Payment", style: Theme.of(context) .textTheme .button .apply(color: Colors.white), ), onPressed: () {}, color: Colors.green, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), ), ) ], ), ), ),

Wrap Up:

At the end of this tutorial you should have a very nice looking multipurpose shop UI.

Do you want me to link this app to a WordPress website? Let me know on Twitter @cybdom.

The more positive response I get the more likely I will post a follow up.