Berikut adalah versi praktikum lengkap untuk membuat Halaman List Product dan Halaman Detail Product menggunakan Flutter terbaru. Modul ini menggunakan data dummy tanpa API service untuk kemudahan implementasi, dengan struktur yang terorganisir dan UI yang modern.


Package yang Perlu Diinstall

Untuk memulai, pastikan Flutter terbaru telah diinstal. Tambahkan package berikut jika diperlukan:

  1. flutter_screenutil (untuk responsivitas)
  2. google_fonts (untuk font yang lebih baik)
  3. flutter_svg (jika Anda ingin menggunakan file SVG untuk ikon)

Perintah untuk menginstall:

flutter pub add flutter_screenutil google_fonts flutter_svg

Struktur Folder Proyek

lib/
├── models/
│   ├── product.dart
├── screens/
│   ├── product_list_screen.dart
│   ├── product_detail_screen.dart
├── widgets/
│   ├── product_card.dart
├── main.dart

1. Model Produk (product.dart)

dart name=lib/models/product.dart
class Product {
  final int id;
  final String name;
  final String description;
  final double price;
  final String imageUrl;

  Product({
    required this.id,
    required this.name,
    required this.description,
    required this.price,
    required this.imageUrl,
  });
}

2. Dummy Data Produk

Tambahkan data dummy langsung di file product_list_screen.dart.


3. Widget Kartu Produk (product_card.dart)

import 'package:flutter/material.dart';
import '../models/product.dart';

class ProductCard extends StatelessWidget {
  final Product product;
  final VoidCallback onTap;

  const ProductCard({
    Key? key,
    required this.product,
    required this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(15),
            gradient: const LinearGradient(
              colors: [Color(0xFFF6F6F6), Color(0xFFEFEFEF)],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(0.1),
                blurRadius: 10,
                offset: const Offset(0, 5),
              ),
            ],
            border: Border.all(
              color: Colors.grey.shade300,
              width: 1,
            )),
        child: LayoutBuilder(
          builder: (context, constraints) {
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Image Section with fixed aspect ratio
                ClipRRect(
                  borderRadius: const BorderRadius.vertical(
                    top: Radius.circular(15),
                  ),
                  child: SizedBox(
                    width: constraints.maxWidth,
                    height: 250, // Makes it square
                    child: Image.network(
                      product.imageUrl,
                      fit: BoxFit.cover,
                      loadingBuilder: (context, child, loadingProgress) {
                        if (loadingProgress == null) return child;
                        return Center(
                          child: CircularProgressIndicator(
                            value: loadingProgress.expectedTotalBytes != null
                                ? loadingProgress.cumulativeBytesLoaded /
                                    loadingProgress.expectedTotalBytes!
                                : null,
                          ),
                        );
                      },
                      errorBuilder: (context, error, stackTrace) {
                        return const Center(
                          child: Icon(
                            Icons.broken_image,
                            size: 50,
                            color: Colors.grey,
                          ),
                        );
                      },
                    ),
                  ),
                ),
                // Product Info Section
                Padding(
                  padding: const EdgeInsets.all(12.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // Product Name
                      SizedBox(
                        width: constraints.maxWidth - 24, // Account for padding
                        child: Text(
                          product.name,
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                          style:
                              Theme.of(context).textTheme.headline6?.copyWith(
                                    fontSize: 16,
                                    fontWeight: FontWeight.bold,
                                  ),
                        ),
                      ),
                      const SizedBox(height: 4),
                      // Product Price
                      Text(
                        '\$${product.price.toStringAsFixed(2)}',
                        style: const TextStyle(
                          fontSize: 14,
                          color: Colors.green,
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      const SizedBox(height: 8),
                      // Action Buttons
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Flexible(
                            child: ElevatedButton(
                              onPressed: onTap,
                              style: ElevatedButton.styleFrom(
                                backgroundColor: Colors.blueAccent,
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(8),
                                ),
                                minimumSize: const Size(0, 36), // Fixed height
                              ),
                              child: const FittedBox(
                                child: Text(
                                  "View Details",
                                  style: TextStyle(fontSize: 12),
                                ),
                              ),
                            ),
                          ),
                          IconButton(
                            onPressed: () {
                              // Add to cart action
                            },
                            icon: const Icon(Icons.shopping_cart_outlined),
                            color: Colors.black54,
                            padding: EdgeInsets.zero, // Remove default padding
                            constraints: const BoxConstraints(
                              maxWidth: 40,
                              maxHeight: 40,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

4. Halaman Daftar Produk (product_list_screen.dart)

dart name=lib/screens/product_list_screen.dart
import 'package:flutter/material.dart';
import '../models/product.dart';
import '../widgets/product_card.dart';
import 'product_detail_screen.dart';

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

  // Dummy data
  final List products = [
    Product(
      id: 1,
      name: 'Wireless Headphones',
      description: 'High-quality wireless headphones with noise cancellation.',
      price: 99.99,
      imageUrl: 'https://via.placeholder.com/150',
    ),
    Product(
      id: 2,
      name: 'Smart Watch',
      description: 'Keep track of your health and notifications.',
      price: 199.99,
      imageUrl: 'https://via.placeholder.com/150',
    ),
    Product(
      id: 3,
      name: 'Gaming Mouse',
      description: 'Ergonomic design with customizable buttons.',
      price: 49.99,
      imageUrl: 'https://via.placeholder.com/150',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Product List'),
        centerTitle: true,
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(10),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
        itemCount: products.length,
        itemBuilder: (context, index) {
          return ProductCard(
            product: products[index],
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ProductDetailScreen(product: products[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

5. Halaman Detail Produk (product_detail_screen.dart)

dart name=lib/screens/product_detail_screen.dart
import 'package:flutter/material.dart';
import '../models/product.dart';

class ProductDetailScreen extends StatelessWidget {
  final Product product;

  const ProductDetailScreen({Key? key, required this.product}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Image.network(
              product.imageUrl,
              height: 200,
              width: double.infinity,
              fit: BoxFit.cover,
            ),
            const SizedBox(height: 20),
            Text(
              product.name,
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Text(
              '\$${product.price.toStringAsFixed(2)}',
              style: const TextStyle(fontSize: 20, color: Colors.green),
            ),
            const SizedBox(height: 20),
            Text(
              product.description,
              style: const TextStyle(fontSize: 16),
            ),
          ],
        ),
      ),
    );
  }
}

6. Main File (main.dart)

dart name=lib/main.dart
import 'package:flutter/material.dart';
import 'screens/product_list_screen.dart';

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,
      ),
      home: ProductListScreen(),
    );
  }
}

Penjelasan

  1. Arsitektur Modular:

    • Folder models untuk model data.
    • Folder screens untuk tampilan halaman.
    • Folder widgets untuk komponen UI yang dapat digunakan kembali.
  2. UI Modern:

    • GridView untuk menampilkan produk dalam bentuk grid.
    • Card widget untuk menampilkan setiap produk dengan gambar, nama, dan harga.
  3. Navigasi:

    • Menggunakan Navigator untuk berpindah antar halaman.
  4. Paket Tambahan:

    • flutter_screenutil untuk responsivitas (opsional).
    • google_fonts untuk gaya font yang lebih menarik (opsional).
  5. Dummy Data:

    • Data dummy digunakan langsung di widget untuk menyederhanakan praktikum tanpa API.

Dengan modul ini, Anda dapat mempelajari dasar-dasar Flutter untuk membuat aplikasi yang terstruktur dan mudah dikembangkan. Anda dapat menambahkan fitur lebih lanjut seperti filter produk atau integrasi API di masa mendatang.