Donut App Flutter UI

Jackie Moraa
5 min readDec 7, 2022

There was a time in my life where I was addicted to donuts (doughnuts). And not just any donuts, but the Tesco lemon filled donuts that came in a pack of 5. I could clear that pack in a 10 minute sitting and still crave for more. In my defence though, I was studying and I needed the calories — I think.

Photo by ELISA KERSCHBAUMER on Unsplash

Now fast forward to last week when I came across this tutorial on YouTube on a Donut App UI. It’s a quick tutorial which I found super exciting. (Shoutout to all tech content creators!). The tutorial only implements the initial UI, so I proceeded to extend it and implemented the detail screen. If you have no idea what I am referring to by ‘initial’ screen and ‘detail’ screen, you can checkout Mitch Koko’s tutorial below or this Dribble page which is the inspiration for the UI designs.

The detail screen:
To get the detail screen, we’ll need to navigate to the new page with quite a number of information as shown in the UI design linked above. We’ll need the donut ingredients, details, image and the price. We hardcoded these values in the initial screen as lists.

List donuts = [    
// [donutFlavor, donutPrice, donutColor, donutImage]
['Ice Cream', '36', Colors.blue, 'assets/images/icecream_donut.png'],
['Strawberry', '45', Colors.red, 'assets/images/strawberry_donut.png'],
['Grape', '84', Colors.purple, 'assets/images/grape_donut.png'],
['Chocolate', '95', Colors.brown, 'assets/images/chocolate_donut.png'],
];

List nutritionalValue = [
// sugar, salt, fat, energy
[['8 Gram', '2%'], ['8 Gram', '.3%'], ['8 Gram', '12%'], ['140 Kcal', '40%']],
[['9 Gram', '3%'], ['9 Gram', '.4%'], ['9 Gram', '11%'], ['110 Kcal', '43%']],
[['6 Gram', '2%'], ['6 Gram', '.3%'], ['6 Gram', '11%'], ['150 Kcal', '32%']],
[['10 Gram', '4%'], ['10 Gram', '.5%'], ['10 Gram', '13%'], ['170 Kcal', '61%']],
];

List donutDetails = [
// donut description
"Ice cream donuts come decorated with rainbow sprinkles and coated with either milk or white chocolate. "
"Inside, you’ll find creamy vanilla ice cream. What more could you want on a warm summer’s day.",
"These incredibly delicious, super easy baked strawberry donuts are packed with fresh strawberries, "
"dipped in strawberry glaze, and so tender and moist they almost melt in your mouth. The aroma of fresh "
"strawberries makes everyone smile.",
"The grape donut maintains the most unique flavors among donuts. They are as light-as-air and filled with "
"a nice, tasty grape jelly. They are showered with powdered sugar for the coveted 'oohs and aahs'.",
"Enjoy these moist and fluffy baked chocolate donuts full of chocolate flavor. Covered in a thick, white or dark "
"chocolate glaze, these are perfect for any chocoholic.",
];

To navigate the details to the next screen, return a GestureDetector on the GridView. And on the onTap() method of the GestureDetector widget, add all the values to be passed to the detail screen like below.

return GestureDetector(
onTap: (){
Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (_) => DetailPage(
donutFlavor: donuts[index][0],
donutPrice: donuts[index][1],
donutColor: donuts[index][2],
imageName: donuts[index][3],
sugar: nutritionalValue[index][0],
salt: nutritionalValue[index][1],
fat: nutritionalValue[index][2],
energy: nutritionalValue[index][3],
donutDetails: donutDetails[index],
),
),
);
},
child: DonutTile(
donutFlavor: donuts[index][0],
donutPrice: donuts[index][1],
donutColor: donuts[index][2],
imageName: donuts[index][3],
),
);

Now that you have all the details passed to the next screen i.e. detail_page.dart, the next step is to create the UI and represent this data. The layout, as per the UI design, has one container sort of overlapping another. For this, the Stack widget is better placed to achieve the design. I talked about stacking containers in this post. The first container in the stack will hold the image while the second container will hold the other details.

body: Stack(
children: [
Container( // image container
color: widget.donutColor[50],
child: Padding(
padding: const EdgeInsets.all(46.0),
child: Image.asset(widget.imageName),
),
),
Align(
alignment: Alignment.bottomCenter,
child: LayoutBuilder( // other details
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
height: constraints.maxHeight / 1.9,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only( // to achieve the curve effect at the top edges
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
),
child: Align(
alignment: Alignment.topLeft,
child: Column(
children: [

// ingredients label - text
// nutritional value tiles - row of tiles see below
// donut details - text
// add to cart container - text in a container

],
),
),
);
},
),
),
],
),

Aside from the nutritional values, the rest of the details are mainly text (from the data passed from the previous screen) which will be displayed in a text widget in the column.

For the nutritional value(NV), create a file e.g. sugar_tile.dart to hold the sugar NV. The widget returned is a small container, with a black border and a border radius of 30 (for the curvature). The child widgets will be the title i.e. sugar, salt, fat or energy, the grams and the percentage (in a coloured circle). All these will be in a column. The resulting code for the sugar tile will be like below.

import 'package:flutter/material.dart';

class SugarTile extends StatelessWidget {
final String title;
final String grams;
final String percentage;

final double borderRadius = 12.0;

const SugarTile({
Key? key,
required this.title,
required this.grams,
required this.percentage,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Container(
height: 100,
width: 60,
decoration: BoxDecoration(
color: Colors.white70,
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.black54,
)
),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
// name
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.0,
color: Colors.grey[700],
),
),
const SizedBox(
height: 4,
),
// grams
Text(
grams,
style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 10.0,
color: Colors.grey[500],
),
),
const SizedBox(
height: 4,
),
// percentage
Container(
height: 40,
width: 40,
decoration: BoxDecoration(
color: Colors.blue[100],
borderRadius: BorderRadius.circular(50)
),
child: Center(
child: Text(
percentage,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.0,
color: Colors.grey[600],
),
),
),
)
],
),
),
),
);
}
}

Add these NV tiles in the details.dart page in a row with the grams and percentage values from the previous screen.

Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SugarTile(
title: 'Sugar',
grams: widget.sugar[0],
percentage: widget.sugar[1],
),
SaltTile(
title: 'Salt',
grams: widget.salt[0],
percentage: widget.salt[1],
),
FatTile(
title: 'Fat',
grams: widget.fat[0],
percentage: widget.fat[1],
),
EnergyTile(
title: 'Energy',
grams: widget.energy[0],
percentage: widget.energy[1],
),
],
),

After putting everything together this is the resulting Flutter UI that I have!

Check out the full code for the initial screen on Mitch Koko’s GitHub page here. For my full implementation including the detail screen, check out my repo here.

“Abs sure are great, but have you ever heard of doughnuts?”
— unknown

Thank you for reading ❤

--

--