image

25 Apr 2026

9K

35K

Building a Product Detail Page Widget with Related Items Carousel and Review Section in Flutter

A Product Detail Page (PDP) is a critical component of any e-commerce application, serving as the central hub where users learn about a product before making a purchase decision. A well-designed PDP not only displays essential product information but also enhances user engagement through features like related item recommendations and customer reviews. This article will guide you through building a professional and functional Product Detail Page widget in Flutter, incorporating these key elements.

Core Components of a Product Detail Page

A robust PDP typically includes:

  • Product Images: High-quality visuals, often in a gallery or carousel.
  • Product Information: Name, price, description, and available options (e.g., size, color).
  • Call-to-Action: "Add to Cart" or "Buy Now" button.
  • Related Items Carousel: Suggestions for similar or complementary products.
  • Review Section: User ratings and written reviews, crucial for social proof.

Flutter Project Setup and Product Model

Assuming you have a basic Flutter project set up, we'll start by defining a simple data model for our Product and Review objects. This will help structure the data we display on the PDP.

Product Model (product.dart)


class Product {
  final String id;
  final String name;
  final String imageUrl;
  final double price;
  final String description;
  final double averageRating;
  final int totalReviews;
  final List<Review> reviews;
  final List<String> relatedProductIds; // For simplicity, just IDs

  Product({
    required this.id,
    required this.name,
    required this.imageUrl,
    required this.price,
    required this.description,
    required this.averageRating,
    required this.totalReviews,
    required this.reviews,
    required this.relatedProductIds,
  });
}

class Review {
  final String reviewerName;
  final double rating;
  final String comment;
  final DateTime date;

  Review({
    required this.reviewerName,
    required this.rating,
    required this.comment,
    required this.date,
  });
}

// Mock data for demonstration
final List<Product> mockProducts = [
  Product(
    id: 'p1',
    name: 'Stylish Running Shoes',
    imageUrl: 'https://via.placeholder.com/600x400/FF5733/FFFFFF?text=Running+Shoes',
    price: 79.99,
    description: 'Lightweight and comfortable running shoes perfect for your daily jog. Features breathable mesh and responsive cushioning.',
    averageRating: 4.5,
    totalReviews: 120,
    reviews: [
      Review(reviewerName: 'Alice Johnson', rating: 5.0, comment: 'Absolutely love these shoes! Super comfortable.', date: DateTime(2023, 10, 26)),
      Review(reviewerName: 'Bob Williams', rating: 4.0, comment: 'Good value for money, slightly tight fit.', date: DateTime(2023, 10, 25)),
    ],
    relatedProductIds: ['p2', 'p3'],
  ),
  Product(
    id: 'p2',
    name: 'Wireless Bluetooth Earbuds',
    imageUrl: 'https://via.placeholder.com/600x400/33FF57/FFFFFF?text=Earbuds',
    price: 49.99,
    description: 'Immersive sound experience with long-lasting battery life. Perfect for workouts and daily commutes.',
    averageRating: 4.8,
    totalReviews: 85,
    reviews: [
      Review(reviewerName: 'Charlie Green', rating: 5.0, comment: 'Amazing sound quality and battery!', date: DateTime(2023, 10, 27)),
    ],
    relatedProductIds: ['p1', 'p4'],
  ),
  Product(
    id: 'p3',
    name: 'Smart Fitness Tracker',
    imageUrl: 'https://via.placeholder.com/600x400/3357FF/FFFFFF?text=Fitness+Tracker',
    price: 129.99,
    description: 'Monitor your health and fitness with advanced sensors. Track steps, heart rate, sleep, and more.',
    averageRating: 4.2,
    totalReviews: 60,
    reviews: [
      Review(reviewerName: 'Diana Prince', rating: 4.0, comment: 'Does everything I need, great app.', date: DateTime(2023, 10, 28)),
    ],
    relatedProductIds: ['p1', 'p2'],
  ),
  Product(
    id: 'p4',
    name: 'Portable Power Bank',
    imageUrl: 'https://via.placeholder.com/600x400/FFFF33/000000?text=Power+Bank',
    price: 29.99,
    description: 'Keep your devices charged on the go with this high-capacity power bank. Sleek design and fast charging.',
    averageRating: 4.6,
    totalReviews: 95,
    reviews: [
      Review(reviewerName: 'Eve Adams', rating: 5.0, comment: 'Essential for travel, charges quickly.', date: DateTime(2023, 10, 29)),
    ],
    relatedProductIds: ['p2', 'p3'],
  ),
];

Product? getProductById(String id) {
  try {
    return mockProducts.firstWhere((product) => product.id == id);
  } catch (e) {
    return null;
  }
}

Product Detail Page Widget Structure

We'll create a ProductDetailPage widget that takes a productId as an argument. This page will use a SingleChildScrollView to ensure all content is scrollable, especially on smaller screens.


import 'package:flutter/material.dart';
import 'product.dart'; // Import the product model

class ProductDetailPage extends StatelessWidget {
  final String productId;

  const ProductDetailPage({Key? key, required this.productId}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final Product? product = getProductById(productId);

    if (product == null) {
      return Scaffold(
        appBar: AppBar(title: const Text('Product Not Found')),
        body: const Center(
          child: Text('The requested product could not be found.'),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
        actions: [
          IconButton(
            icon: const Icon(Icons.share),
            onPressed: () {
              // Share product functionality
            },
          ),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 1. Product Image
            Image.network(
              product.imageUrl,
              fit: BoxFit.cover,
              width: double.infinity,
              height: 300,
            ),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // 2. Product Name & Price
                  Text(
                    product.name,
                    style: Theme.of(context).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8.0),
                  Text(
                    '\$${product.price.toStringAsFixed(2)}',
                    style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.deepOrange),
                  ),
                  const SizedBox(height: 16.0),

                  // 3. Product Description
                  Text(
                    product.description,
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                  const SizedBox(height: 24.0),

                  // 4. Add to Cart Button
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed: () {
                        // Add to cart logic
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(content: Text('${product.name} added to cart!')),
                        );
                      },
                      icon: const Icon(Icons.shopping_cart),
                      label: const Text('Add to Cart', style: TextStyle(fontSize: 18)),
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 12.0),
                        backgroundColor: Colors.deepOrange,
                        foregroundColor: Colors.white,
                      ),
                    ),
                  ),
                  const SizedBox(height: 32.0),

                  // 5. Related Items Carousel
                  _buildRelatedItemsSection(context, product.relatedProductIds),
                  const SizedBox(height: 32.0),

                  // 6. Review Section
                  _buildReviewSection(context, product),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildRelatedItemsSection(BuildContext context, List<String> relatedProductIds) {
    if (relatedProductIds.isEmpty) {
      return const SizedBox.shrink();
    }

    final List<Product> relatedProducts = relatedProductIds
        .map((id) => getProductById(id))
        .whereType<Product>() // Filter out nulls
        .toList();

    if (relatedProducts.isEmpty) {
      return const SizedBox.shrink();
    }

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'You might also like',
          style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 16.0),
        SizedBox(
          height: 200, // Height for the horizontal carousel
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: relatedProducts.length,
            itemBuilder: (context, index) {
              final relatedProduct = relatedProducts[index];
              return Padding(
                padding: const EdgeInsets.only(right: 16.0),
                child: GestureDetector(
                  onTap: () {
                    // Navigate to related product detail page
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => ProductDetailPage(productId: relatedProduct.id),
                      ),
                    );
                  },
                  child: Card(
                    elevation: 4.0,
                    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
                    child: SizedBox(
                      width: 150,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          ClipRRect(
                            borderRadius: const BorderRadius.vertical(top: Radius.circular(12.0)),
                            child: Image.network(
                              relatedProduct.imageUrl,
                              height: 100,
                              width: 150,
                              fit: BoxFit.cover,
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  relatedProduct.name,
                                  style: Theme.of(context).textTheme.titleSmall,
                                  maxLines: 1,
                                  overflow: TextOverflow.ellipsis,
                                ),
                                const SizedBox(height: 4.0),
                                Text(
                                  '\$${relatedProduct.price.toStringAsFixed(2)}',
                                  style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold, color: Colors.deepOrange),
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }

  Widget _buildReviewSection(BuildContext context, Product product) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Customer Reviews',
          style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 16.0),
        Row(
          children: [
            Icon(Icons.star, color: Colors.amber[700], size: 30),
            const SizedBox(width: 8.0),
            Text(
              '${product.averageRating.toStringAsFixed(1)} out of 5',
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(width: 8.0),
            Text(
              '(${product.totalReviews} reviews)',
              style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Colors.grey[600]),
            ),
          ],
        ),
        const SizedBox(height: 24.0),
        if (product.reviews.isEmpty)
          const Text('No reviews yet. Be the first to review this product!')
        else
          ListView.builder(
            shrinkWrap: true, // Important for nested ListViews
            physics: const NeverScrollableScrollPhysics(), // Important for nested ListViews
            itemCount: product.reviews.length,
            itemBuilder: (context, index) {
              final review = product.reviews[index];
              return Card(
                margin: const EdgeInsets.only(bottom: 16.0),
                elevation: 2.0,
                shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Text(
                            review.reviewerName,
                            style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
                          ),
                          const Spacer(),
                          Row(
                            children: List.generate(5, (starIndex) {
                              return Icon(
                                starIndex < review.rating ? Icons.star : Icons.star_border,
                                color: Colors.amber,
                                size: 18,
                              );
                            }),
                          ),
                        ],
                      ),
                      const SizedBox(height: 8.0),
                      Text(
                        review.comment,
                        style: Theme.of(context).textTheme.bodyMedium,
                      ),
                      const SizedBox(height: 8.0),
                      Align(
                        alignment: Alignment.bottomRight,
                        child: Text(
                          '${review.date.day}/${review.date.month}/${review.date.year}',
                          style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey[600]),
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
      ],
    );
  }
}

Usage Example (main.dart)

To see the PDP in action, you can integrate it into your main.dart file.


import 'package:flutter/material.dart';
import 'product_detail_page.dart'; // Import your PDP widget

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Product App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.white,
          foregroundColor: Colors.black,
          elevation: 0,
        ),
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Product Listing'),
      ),
      body: ListView.builder(
        itemCount: mockProducts.length,
        itemBuilder: (context, index) {
          final product = mockProducts[index];
          return Card(
            margin: const EdgeInsets.all(8.0),
            elevation: 4.0,
            child: ListTile(
              leading: Image.network(
                product.imageUrl,
                width: 60,
                height: 60,
                fit: BoxFit.cover,
              ),
              title: Text(product.name),
              subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => ProductDetailPage(productId: product.id),
                  ),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

Explanation of Key Sections:

Product Information Section:

The main product details like image, name, price, and description are laid out using a Column within a Padding widget for consistent spacing. An Image.network displays the product image, and Text widgets handle the textual information. The "Add to Cart" button is an ElevatedButton.icon styled for prominence.

Related Items Carousel:

The _buildRelatedItemsSection method uses a horizontal ListView.builder wrapped in a SizedBox to control its height. Each related product is displayed within a Card, which includes its image, name, and price. Tapping on a related item navigates to its own ProductDetailPage, demonstrating simple navigation.

Review Section:

The _buildReviewSection displays the overall average rating and total number of reviews. Individual reviews are rendered using another ListView.builder. It's crucial to set shrinkWrap: true and physics: const NeverScrollableScrollPhysics() for the nested ListView.builder of reviews to prevent scroll conflicts with the parent SingleChildScrollView.

Conclusion

By following these steps, you can create a feature-rich Product Detail Page in Flutter that not only showcases your products effectively but also enhances the user experience through engaging features like related item carousels and detailed customer reviews. This modular approach allows for easy maintenance and expansion, enabling you to add more functionalities like product variations, wishlists, or dynamic content loading from APIs in the future.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is