Building a Dynamic Product List with GetX in Flutter
Learn how to create a responsive product screen using GetX in Flutter. This tutorial covers API integration, pagination, cart management, and quantity selection with efficient state management. Perfect for building an e-commerce app!
class ProductScreenController extends GetxController{
RxList<Product> productList = <Product>[].obs;
RxList<Product> cartList = <Product>[].obs;
RxMap<int , int> quantityMap = <int , int>{}.obs;
RxMap<int, bool> showQuantitySelector = <int, bool>{}.obs; // ✅ Track Visibility of Quantity Selector
RxBool isLoading = false.obs;
RxBool isLoadingMore = false.obs;
final Dio dio = Dio();
int limit = 10;
int skip = 0;
bool hasMore = true; // ✅ Track if more data is available
@override
void onInit() { // ✅ Automatically Call API when Controller initializes
super.onInit();
fetchData();
}
Future<void> fetchData({bool isPagination = false}) async {
if (!hasMore) return;
try {
if (!isPagination) {
isLoading.value = true;
} else {
isLoadingMore.value = true; // ✅ Show loading indicator at bottom
}
final response = await dio.get(
'https://dummyjson.com/products',
queryParameters: {
'limit': limit,
'skip': skip,
},
);
if (response.statusCode == 200) {
ProductScreenModel productScreenModel = ProductScreenModel.fromMap(response.data);
if (productScreenModel.products.isEmpty) {
hasMore = false; // ✅ No more products available
} else {
productList.addAll(productScreenModel.products); // ✅ Add new data
skip += limit; // ✅ Update skip for next page
}
} else {
Get.snackbar("Error", "Data not Found");
}
} catch (e) {
Get.snackbar("Error", "Error Found: $e");
} finally {
isLoading.value = false;
isLoadingMore.value = false;
}
}
// ✅ Toggle Quantity Selector Visibility
void toggleQuantitySelector(int productId) {
showQuantitySelector[productId] = !(showQuantitySelector[productId] ?? false);
if (showQuantitySelector[productId] == true) {
addToCart(productList.firstWhere((p) => p.id == productId));
}
}
void addToCart(Product product){
cartList.add(product);
if(cartList.contains(product)){
quantityMap[product.id] = 1;
}else{
quantityMap[product.id] = (quantityMap[product.id] ?? 0) + 1;
}
Get.snackbar("Added to Cart", "${product.title} added successfully");
}
void removeFromCart(Product product){
cartList.remove(product);
quantityMap.remove(product.id);
Get.snackbar("Added to Cart", "${product.title} added successfully");
showQuantitySelector[product.id] = false; // Hide selector on removal
Get.snackbar("Removed", "${product.title} removed from cart!");
}
void increaseQuantity(int productId){
if(quantityMap.containsKey(productId)){
quantityMap[productId] = quantityMap[productId]! + 1;
}
}
void decreaseQuantity(int productId){
if(quantityMap.containsKey(productId) && quantityMap[productId]! > 1){
quantityMap[productId] = quantityMap[productId]! - 1;
}else{
removeFromCart(productList.firstWhere((p) => p.id == productId));
showQuantitySelector[productId] = false;
}
}
}
You'll learn how to manage UI states, handle errors gracefully, and implement smooth user interactions. By the end, you'll have a functional product listing with add-to-cart functionality—perfect for e-commerce applications
First screen
Cart screen
class CartScreen extends StatelessWidget {
final ProductScreenController controller = Get.find<ProductScreenController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
centerTitle: true,
),
body: Obx(() {
if (controller.cartList.isEmpty) {
return Center(child: Text('Your Cart is Empty!'));
}
return ListView.builder(
itemCount: controller.cartList.length,
itemBuilder: (context, index) {
var product = controller.cartList[index];
return Card(
margin: EdgeInsets.all(10),
child: ListTile(
leading: Image.network(product.thumbnail, width: 50, height: 50, fit: BoxFit.cover),
title: Text(product.title, style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text("Price: ₹${product.price}"),
trailing:Obx((){
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.remove, color: Colors.red),
onPressed: () {
controller.decreaseQuantity(product.id);
},
),
Text("${controller.quantityMap[product.id] ?? 1}"),
IconButton(
icon: Icon(Icons.add, color: Colors.green),
onPressed: () {
controller.increaseQuantity(product.id);
},
),
],
);
})
),
);
},
);
}),
);
}
}
Model
ProductScreenModel productScreenModelFromMap(String str) => ProductScreenModel.fromMap(json.decode(str));
String productScreenModelToMap(ProductScreenModel data) => json.encode(data.toMap());
class ProductScreenModel {
List<Product> products;
int total;
int skip;
int limit;
ProductScreenModel({
required this.products,
required this.total,
required this.skip,
required this.limit,
});
factory ProductScreenModel.fromMap(Map<String, dynamic> json) => ProductScreenModel(
products: List<Product>.from(json["products"].map((x) => Product.fromMap(x))),
total: json["total"],
skip: json["skip"],
limit: json["limit"],
);
Map<String, dynamic> toMap() => {
"products": List<dynamic>.from(products.map((x) => x.toMap())),
"total": total,
"skip": skip,
"limit": limit,
};
}
class Product {
int id;
String title;
String description;
String category;
double price;
double discountPercentage;
double rating;
int stock;
List<String> tags;
String brand;
String sku;
int weight;
Dimensions dimensions;
String warrantyInformation;
String shippingInformation;
String availabilityStatus;
List<Review> reviews;
String returnPolicy;
int minimumOrderQuantity;
Meta meta;
String thumbnail;
List<String> images;
Product({
required this.id,
required this.title,
required this.description,
required this.category,
required this.price,
required this.discountPercentage,
required this.rating,
required this.stock,
required this.tags,
required this.brand,
required this.sku,
required this.weight,
required this.dimensions,
required this.warrantyInformation,
required this.shippingInformation,
required this.availabilityStatus,
required this.reviews,
required this.returnPolicy,
required this.minimumOrderQuantity,
required this.meta,
required this.thumbnail,
required this.images,
});
factory Product.fromMap(Map<String, dynamic> json) => Product(
id: json["id"] ?? 0,
title: json["title"] ?? "",
description: json["description"] ?? "",
category: json["category"]?? "",
price: json["price"]?.toDouble()?? 0,
discountPercentage: json["discountPercentage"]?.toDouble()?? 0,
rating: json["rating"]?.toDouble() ?? 0,
stock: json["stock"] ?? "",
tags: List<String>.from(json["tags"].map((x) => x)),
brand: json["brand"]?? "",
sku: json["sku"],
weight: json["weight"],
dimensions: Dimensions.fromMap(json["dimensions"]),
warrantyInformation: json["warrantyInformation"],
shippingInformation: json["shippingInformation"],
availabilityStatus: json["availabilityStatus"],
reviews: List<Review>.from(json["reviews"].map((x) => Review.fromMap(x))),
returnPolicy: json["returnPolicy"],
minimumOrderQuantity: json["minimumOrderQuantity"],
meta: Meta.fromMap(json["meta"]),
thumbnail: json["thumbnail"],
images: List<String>.from(json["images"].map((x) => x)),
);
Map<String, dynamic> toMap() => {
"id": id,
"title": title,
"description": description,
"category": category,
"price": price,
"discountPercentage": discountPercentage,
"rating": rating,
"stock": stock,
"tags": List<dynamic>.from(tags.map((x) => x)),
"brand": brand,
"sku": sku,
"weight": weight,
"dimensions": dimensions.toMap(),
"warrantyInformation": warrantyInformation,
"shippingInformation": shippingInformation,
"availabilityStatus": availabilityStatus,
"reviews": List<dynamic>.from(reviews.map((x) => x.toMap())),
"returnPolicy": returnPolicy,
"minimumOrderQuantity": minimumOrderQuantity,
"meta": meta.toMap(),
"thumbnail": thumbnail,
"images": List<dynamic>.from(images.map((x) => x)),
};
}
class Dimensions {
double width;
double height;
double depth;
Dimensions({
required this.width,
required this.height,
required this.depth,
});
factory Dimensions.fromMap(Map<String, dynamic> json) => Dimensions(
width: json["width"]?.toDouble(),
height: json["height"]?.toDouble(),
depth: json["depth"]?.toDouble(),
);
Map<String, dynamic> toMap() => {
"width": width,
"height": height,
"depth": depth,
};
}
class Meta {
DateTime createdAt;
DateTime updatedAt;
String barcode;
String qrCode;
Meta({
required this.createdAt,
required this.updatedAt,
required this.barcode,
required this.qrCode,
});
factory Meta.fromMap(Map<String, dynamic> json) => Meta(
createdAt: DateTime.parse(json["createdAt"]),
updatedAt: DateTime.parse(json["updatedAt"]),
barcode: json["barcode"],
qrCode: json["qrCode"],
);
Map<String, dynamic> toMap() => {
"createdAt": createdAt.toIso8601String(),
"updatedAt": updatedAt.toIso8601String(),
"barcode": barcode,
"qrCode": qrCode,
};
}
class Review {
int rating;
String comment;
DateTime date;
String reviewerName;
String reviewerEmail;
Review({
required this.rating,
required this.comment,
required this.date,
required this.reviewerName,
required this.reviewerEmail,
});
factory Review.fromMap(Map<String, dynamic> json) => Review(
rating: json["rating"],
comment: json["comment"],
date: DateTime.parse(json["date"]),
reviewerName: json["reviewerName"],
reviewerEmail: json["reviewerEmail"],
);
Map<String, dynamic> toMap() => {
"rating": rating,
"comment": comment,
"date": date.toIso8601String(),
"reviewerName": reviewerName,
"reviewerEmail": reviewerEmail,
};
}
Comments
Post a Comment