Menu
CoddyTech

Store: The Product Grid

Part of the Projects section of Coddy's Flutter journey. Lesson 19 of 36.

The third app is a small store: a grid of products, a page for each one, a cart and a checkout. It is the shape of most shopping apps, and it brings back Provider and intl.

The products are in the starter, each with a name, a price, an icon, a color and a description. A store shows them in a grid, two to a row on a phone:

GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16),
  mainAxisSpacing: 12,
  crossAxisSpacing: 12,
  childAspectRatio: 0.8,
  children: [for (Product product in products) ProductCard(product: product)],
)

childAspectRatio is width divided by height: 0.8 makes each card a little taller than wide, room for a picture and two lines of text.

Each card is its own widget, ProductCard, that takes a Product. Inside, an Expanded tile fills the space above the name. Its color is the lightest shade of the product's color, product.color.shade50, with the icon in the full color on top. That is why color is a MaterialColor: it has the shades.

challenge icon

Challenge

Medium

Show the products:

  • write a ProductCard widget that takes a required product
  • it is a Card (with clipBehavior: Clip.antiAlias) holding a Column: an Expanded full-width Container in product.color.shade50 with the product's icon in the middle (size 56, product.color), then the name in titleMedium with 12 pixels of padding
  • replace the Center with the GridView.count from the theory

Try it yourself

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
      ),
      home: const ShopScreen(),
    );
  }
}

class Product {
  const Product({
    required this.name,
    required this.price,
    required this.icon,
    required this.color,
    required this.description,
  });

  final String name;
  final double price;
  final IconData icon;
  final MaterialColor color;
  final String description;
}

const List<Product> products = [
  Product(
    name: 'Headphones',
    price: 59,
    icon: Icons.headphones,
    color: Colors.indigo,
    description: 'Wireless headphones with 30 hours of battery.',
  ),
  Product(
    name: 'Watch',
    price: 129,
    icon: Icons.watch,
    color: Colors.teal,
    description: 'A smart watch that counts your steps.',
  ),
  Product(
    name: 'Backpack',
    price: 45.5,
    icon: Icons.backpack,
    color: Colors.orange,
    description: 'Room for a laptop, a lunch and a jacket.',
  ),
  Product(
    name: 'Mug',
    price: 12,
    icon: Icons.coffee,
    color: Colors.brown,
    description: 'Holds a big coffee and keeps it warm.',
  ),
  Product(
    name: 'Camera',
    price: 1299,
    icon: Icons.photo_camera,
    color: Colors.blueGrey,
    description: 'A mirrorless camera for sharp photos.',
  ),
  Product(
    name: 'Sneakers',
    price: 89.99,
    icon: Icons.directions_run,
    color: Colors.red,
    description: 'Light running shoes for every day.',
  ),
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Coddy Shop')),
      body: const Center(
        child: Text('Products go here'),
      ),
    );
  }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Projects

Practice on your own: Online Flutter compiler