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:
-
flutter_screenutil
(untuk responsivitas) -
google_fonts
(untuk font yang lebih baik) -
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
-
Arsitektur Modular:
- Folder
models
untuk model data. - Folder
screens
untuk tampilan halaman. - Folder
widgets
untuk komponen UI yang dapat digunakan kembali.
- Folder
-
UI Modern:
- GridView untuk menampilkan produk dalam bentuk grid.
- Card widget untuk menampilkan setiap produk dengan gambar, nama, dan harga.
-
Navigasi:
- Menggunakan
Navigator
untuk berpindah antar halaman.
- Menggunakan
-
Paket Tambahan:
-
flutter_screenutil
untuk responsivitas (opsional). -
google_fonts
untuk gaya font yang lebih menarik (opsional).
-
-
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.