JIT
debugging order model code for error in order details display
I got an error import 'dart:convert'; import 'package:fitstand/src/models/customer/menu_items_model.dart'; // Assuming OrderItem needs MenuItem // Example Order Model - Adjust fields and types based on your ACTUAL backend response class Order { final int id; final int? userId; final int? restaurantId; final int? addressId; final Map<String, dynamic>? deliveryAddressSnapshot; // Store as Map final double? totalAmount; final String? paymentGateway; final String? paymentId; final String? paymentStatus; final String? orderStatus; final String? razorpayOrderId; final DateTime? orderDate; // Use DateTime for created_at/updated_at final String? restaurantName; // Add if backend includes it via relationship final List<OrderItem>? items; // List of order items final double? subTotal; // Added field final double? deliveryCharge; // Added field Order({ required this.id, this.userId, this.restaurantId, this.addressId, this.deliveryAddressSnapshot, this.totalAmount, this.paymentGateway, this.paymentId, this.paymentStatus, this.orderStatus, this.razorpayOrderId, this.orderDate, this.restaurantName, this.items, this.subTotal, // Added to constructor this.deliveryCharge, // Added to constructor }); factory Order.fromJson(Map<String, dynamic> json) { // Helper to safely parse DateTime DateTime? parseOptionalDate(String? dateString) { if (dateString == null) return null; try { return DateTime.parse(dateString).toLocal(); } // Convert to local time catch (e) { print("Error parsing date: $dateString"); return null; } } // Helper to safely decode JSON string if needed (depends on backend response) Map<String, dynamic>? decodeJsonString(dynamic jsonField) { if (jsonField == null) return null; if (jsonField is Map<String, dynamic>) return jsonField; // Already a map if (jsonField is String) { try { // *** Use the imported jsonDecode *** return jsonDecode(jsonField) as Map<String, dynamic>?; // ********************************** } catch (e) { print("Error decoding JSON string field: $e"); return null; } } return null; // Not a map or string } return Order( id: json['id'] as int? ?? 0, // Handle potential null ID? Unlikely but safe. userId: json['user_id'] as int?, restaurantId: json['restaurant_id'] as int?, addressId: json['address_id'] as int?, // Use helper in case backend sends string OR map deliveryAddressSnapshot: decodeJsonString(json['delivery_address_snapshot']), //totalAmount: double.tryParse(json['total_amount']?.toString() ?? '0.0'), totalAmount: _parseDouble(json['total_amount']), subTotal: _parseDouble(json['sub_total']), deliveryCharge: _parseDouble(json['delivery_charge']), paymentGateway: json['payment_gateway'] as String?, paymentId: json['payment_id'] as String?, paymentStatus: json['payment_status'] as String?, orderStatus: json['order_status'] as String?, razorpayOrderId: json['razorpay_order_id'] as String?, orderDate: parseOptionalDate(json['created_at']), // Parse created_at // Add restaurant name parsing if backend includes it (e.g., via relationship) restaurantName: json['restaurant']?['name'] as String?, // Example if nested under 'restaurant' // Parse items list if included in the response items: json['items'] != null && json['items'] is List ? (json['items'] as List).map((itemJson) => OrderItem.fromJson(itemJson)).toList() : null, //subTotal: json['sub_total']?.toDouble(), // Added to fromJson //deliveryCharge: json['delivery_charge']?.toDouble(), // Added to fromJson ); } static double? _parseDouble(dynamic value) { if (value == null) return null; if (value is double) return value; if (value is int) return value.toDouble(); if (value is String) return double.tryParse(value); return null; } } class OrderItem { final int id; final int? orderId; final int? menuItemId; final int? quantity; final double? price; final String? specialRequest; // Add fields from related MenuItem if backend includes them final String? menuItemName; final String? menuItemPhoto; final String? menuItemFoodType; // Add food type // You might have the full MenuItem object nested final MenuItem? menuItem; // Example if the full item is nested OrderItem({ required this.id, this.orderId, this.menuItemId, this.quantity, this.price, this.specialRequest, this.menuItemName, this.menuItemPhoto, this.menuItemFoodType, this.menuItem, // Add to constructor }); factory OrderItem.fromJson(Map<String, dynamic> json) { return OrderItem( id: json['id'] as int? ?? 0, orderId: json['order_id'] as int?, menuItemId: json['menu_item_id'] as int?, quantity: json['quantity'] as int?, //price: double.tryParse(json['price']?.toString() ?? '0.0'), price: _parseDouble(json['price']), specialRequest: json['special_request'] as String?, // Parse nested menu item details if backend sends them // Adjust keys ('menu_item', 'item_name', 'photo') based on your actual API response menuItemName: json['menu_item']?['item_name'] as String?, //menuItemPhoto: json['menu_item']?['photo'] as String?, menuItemPhoto: json['menu_item_photo'] ?? json['menu_item']?['photo_url'] ?? json['menu_item']?['photo'], menuItemFoodType: json['menu_item']?['food_type'] as String?, // If the full MenuItem object is nested under 'menu_item': menuItem: json['menu_item'] != null ? MenuItem.fromJson(json['menu_item']) : null, ); } static double? _parseDouble(dynamic value) { if (value == null) return null; if (value is double) return value; if (value is int) return value.toDouble(); if (value is String) return double.tryParse(value); return null; } } when I was trying to see order details. My order_model.dart is: This happens after I have updated code for photo: My order_model.dart code is: import 'dart:convert'; import 'package:fitstand/src/models/customer/menu_items_model.dart'; // Assuming OrderItem needs MenuItem // Example Order Model - Adjust fields and types based on your ACTUAL backend response class Order { final int id; final int? userId; final int? restaurantId; final int? addressId; final Map<String, dynamic>? deliveryAddressSnapshot; // Store as Map final double? totalAmount; final String? paymentGateway; final String? paymentId; final String? paymentStatus; final String? orderStatus; final String? razorpayOrderId; final DateTime? orderDate; // Use DateTime for created_at/updated_at final String? restaurantName; // Add if backend includes it via relationship final List? items; // List of order items final double? subTotal; // Added field final double? deliveryCharge; // Added field Order({ required this.id, this.userId, this.restaurantId, this.addressId, this.deliveryAddressSnapshot, this.totalAmount, this.paymentGateway, this.paymentId, this.paymentStatus, this.orderStatus, this.razorpayOrderId, this.orderDate, this.restaurantName, this.items, this.subTotal, // Added to constructor this.deliveryCharge, // Added to constructor }); factory Order.fromJson(Map<String, dynamic> json) { // Helper to safely parse DateTime DateTime? parseOptionalDate(String? dateString) { if (dateString == null) return null; try { return DateTime.parse(dateString).toLocal(); } // Convert to local time catch (e) { print("Error parsing date: $dateString"); return null; } } // Helper to safely decode JSON string if needed (depends on backend response) Map<String, dynamic>? decodeJsonString(dynamic jsonField) { if (jsonField == null) return null; if (jsonField is Map<String, dynamic>) return jsonField; // Already a map if (jsonField is String) { try { // *** Use the imported jsonDecode *** return jsonDecode(jsonField) as Map<String, dynamic>?; // ********************************** } catch (e) { print("Error decoding JSON string field: $e"); return null; } } return null; // Not a map or string } return Order( id: json['id'] as int? ?? 0, // Handle potential null ID? Unlikely but safe. userId: json['user_id'] as int?, restaurantId: json['restaurant_id'] as int?, addressId: json['address_id'] as int?, // Use helper in case backend sends string OR map deliveryAddressSnapshot: decodeJsonString(json['delivery_address_snapshot']), //totalAmount: double.tryParse(json['total_amount']?.toString() ?? '0.0'), totalAmount: _parseDouble(json['total_amount']), subTotal: _parseDouble(json['sub_total']), deliveryCharge: _parseDouble(json['delivery_charge']), paymentGateway: json['payment_gateway'] as String?, paymentId: json['payment_id'] as String?, paymentStatus: json['payment_status'] as String?, orderStatus: json['order_status'] as String?, razorpayOrderId: json['razorpay_order_id'] as String?, orderDate: parseOptionalDate(json['created_at']), // Parse created_at // Add restaurant name parsing if backend includes it (e.g., via relationship) restaurantName: json['restaurant']?['name'] as String?, // Example if nested under 'restaurant' // Parse items list if included in the response items: json['items'] != null && json['items'] is List ? (json['items'] as List).map((itemJson) => OrderItem.fromJson(itemJson)).toList() : null, //subTotal: json['sub_total']?.toDouble(), // Added to fromJson //deliveryCharge: json['delivery_charge']?.toDouble(), // Added to fromJson ); } static double? _parseDouble(dynamic value) { if (value == null) return null; if (value is double) return value; if (value is int) return value.toDouble(); if (value is String) return double.tryParse(value); return null; } } class OrderItem { final int id; final int? orderId; final int? menuItemId; final int? quantity; final double? price; final String? specialRequest; // Add fields from related MenuItem if backend includes them final String? menuItemName; final String? menuItemPhoto; final String? menuItemFoodType; // Add food type // You might have the full MenuItem object nested final MenuItem? menuItem; // Example if the full item is nested OrderItem({ required this.id, this.orderId, this.menuItemId, this.quantity, this.price, this.specialRequest, this.menuItemName, this.menuItemPhoto, this.menuItemFoodType, this.menuItem, // Add to constructor }); factory OrderItem.fromJson(Map<String, dynamic> json) { return OrderItem( id: json['id'] as int? ?? 0, orderId: json['order_id'] as int?, menuItemId: json['menu_item_id'] as int?, quantity: json['quantity'] as int?, //price: double.tryParse(json['price']?.toString() ?? '0.0'), price: _parseDouble(json['price']), specialRequest: json['special_request'] as String?, // Parse nested menu item details if backend sends them // Adjust keys ('menu_item', 'item_name', 'photo') based on your actual API response menuItemName: json['menu_item']?['item_name'] as String?, //menuItemPhoto: json['menu_item']?['photo'] as String?, menuItemPhoto: json['menu_item_photo'] ?? json['menu_item']?['photo_url'] ?? json['menu_item']?['photo'], menuItemFoodType: json['menu_item']?['food_type'] as String?, // If the full MenuItem object is nested under 'menu_item': menuItem: json['menu_item'] != null ? MenuItem.fromJson(json['menu_item']) : null, ); } static double? _parseDouble(dynamic value) { if (value == null) return null; if (value is double) return value; if (value is int) return value.toDouble(); if (value is String) return double.tryParse(value); return null; } }

Based on the error and the code provided, I'll help fix the order_model.dart file. The main issue appears to be with the photo field handling in the OrderItem class. Here's the corrected version:

_

Updated files
v1
In my following code now I cannot see veg and non-veg indicator beside item name: Widget _buildOrderItemTile(OrderItem item) { final bool isVeg = (item.menuItemFoodType?.toLowerCase() ?? 'non-veg') == 'veg'; final String? imageUrl = item.menuItemPhoto != null && item.menuItemPhoto!.isNotEmpty ? item.menuItemPhoto!.startsWith('http') ? item.menuItemPhoto : 'https://admin.myfitstand.com/public/${item.menuItemPhoto}' : null; return Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image or placeholder if (imageUrl != null) ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( imageUrl, height: 60, width: 60, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _buildImagePlaceholder(isVeg), loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Container( height: 60, width: 60, color: Colors.grey[200], child: Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ), ); }, ), ) else _buildImagePlaceholder(isVeg), const SizedBox(width: 12), // Item details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ if (imageUrl == null) ...[ _buildVegNonVegIndicator(isVeg), const SizedBox(width: 8), ], Expanded( child: Text( '${item.menuItemName ?? 'Item'} (x${item.quantity ?? 1})', style: const TextStyle(fontWeight: FontWeight.bold), ), ), ], ), if (item.specialRequest != null && item.specialRequest!.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 4.0), child: Text( 'Request: ${item.specialRequest}', style: TextStyle( fontSize: 12, color: Colors.grey[600], fontStyle: FontStyle.italic, ), ), ), ], ), ), // Price Text( currencyFormatter.format((item.price ?? 0.0) * (item.quantity ?? 1)), style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), ); } Widget _buildImagePlaceholder(bool isVeg) { return Container( height: 60, width: 60, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(8), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.fastfood, size: 24, color: Colors.grey), const SizedBox(height: 4), _buildVegNonVegIndicator(isVeg), ], ), ); } Widget _buildVegNonVegIndicator(bool isVeg) { final Color indicatorColor = isVeg ? _vegColor : _nonVegColor; return Container( margin: const EdgeInsets.only(top: 4.0, right: 0), width: 12, height: 12, decoration: BoxDecoration( border: Border.all(color: indicatorColor, width: 1.5), ), alignment: Alignment.center, child: Container( width: 6, height: 6, decoration: BoxDecoration( color: indicatorColor, shape: BoxShape.circle, ), ), ); } My old code where I was able to see veg or non-veg symbol was: Widget _buildOrderItemTile(OrderItem item) { final bool isVeg = item.menuItemFoodType?.toLowerCase() == 'veg'; return Padding( padding: EdgeInsets.only(top: 3.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (item.menuItemPhoto != null && item.menuItemPhoto!.isNotEmpty) ClipRRect( borderRadius: BorderRadius.circular(4), child: Image.network( item.menuItemPhoto!, height: 60, width: 60, fit: BoxFit.cover, errorBuilder: (c,e,s) => Container( height: 60, width: 60, color: Colors.grey[200], child: Icon(Icons.fastfood, color: Colors.grey[500]), ), ), ), if (item.menuItemPhoto == null || item.menuItemPhoto!.isEmpty) _buildVegNonVegIndicator(isVeg), SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ if (item.menuItemPhoto != null && item.menuItemPhoto!.isNotEmpty) _buildVegNonVegIndicator(isVeg), if (item.menuItemPhoto != null && item.menuItemPhoto!.isNotEmpty) SizedBox(width: 8), Expanded( child: Text( '${item.menuItemName ?? 'Item'} (x${item.quantity ?? 1})', style: TextStyle(fontWeight: FontWeight.w500), ), ), ], ), if (item.specialRequest != null && item.specialRequest!.isNotEmpty) Padding( padding: EdgeInsets.only(top: 3.0), child: Text( 'Request: ${item.specialRequest}', style: TextStyle(fontSize: 12, color: Colors.grey[600], fontStyle: FontStyle.italic), ), ), ], ), ), SizedBox(width: 10), Text(currencyFormatter.format((item.price ?? 0.0) * (item.quantity ?? 1))), ], ), ); } Widget _buildVegNonVegIndicator(bool isVeg) { final Color indicatorColor = isVeg ? _vegColor : _nonVegColor; return Container( margin: const EdgeInsets.only(top: 4.0, right: 0), width: 12, height: 12, decoration: BoxDecoration( border: Border.all(color: indicatorColor, width: 1.5), ), alignment: Alignment.center, child: Container( width: 6, height: 6, decoration: BoxDecoration( color: indicatorColor, shape: BoxShape.circle, ), ), ); }

Here's the fixed version of your _buildOrderItemTile widget that will show the veg/non-veg indicator properly:

The key changes made:

  1. Always show the veg/non-veg indicator next to the item name (removed the conditional rendering)
  2. Simplified the layout structure to ensure consistent indicator placement
  3. Kept all the image loading improvements from your new version
  4. Maintained the same styling for the indicator widget

This will ensure the veg/non-veg indicator is always visible next to the item name, regardless of whether there's an image or not._

Updated files
v2
In my following code can I change inDays from 7 to 1: callFutures() async { var isUserSubscribed = await homeController.checkSubscribed(context); if (mounted) { if (!isUserSubscribed) { String lastShowDateString = await getFreeUserPopUpDate(); bool canShowPopUp = false; if (lastShowDateString == '') { await saveFreeUserPopUpDate( DateFormat('yyyy-MM-dd').format(DateTime.now())); canShowPopUp = true; } else { DateTime lastShowDate = DateFormat('yyyy-MM-dd').parse(lastShowDateString); DateTime currentDate = DateTime.now(); //if (currentDate.difference(lastShowDate).inDays >= 7) { if (currentDate.difference(lastShowDate).inDays >= 1) { canShowPopUp = true; } } if (canShowPopUp) { await customShowDialog( context: context, height: 260.0, padding: const EdgeInsets.all(25.0), width: MediaQuery.of(context).size.width, insetPadding: const EdgeInsets.all(40.0), child: WillPopScope( onWillPop: () { return Future.value(false); }, child: Form( key: homeController.formKey, child: Column( children: [ Text( "Welcome ${homeController.userName},\n Subscribe To One of Our Premium Plan And Enhance Your Fitness Journey!", textAlign: TextAlign.center, style: montserratSemiBold.copyWith( color: blackColor, fontSize: Dimensions.fontSizeDefault, ), ), const Spacer(), CustomButtonWidget( width: MediaQuery.of(context).size.width, height: 36.0, title: viewPremiumPlansTxt, border: null, titleColor: whiteColor, fontSize: Dimensions.fontSizeLarge, borderRadius: BorderRadius.circular(6.0), backgroundColor: HexColor(americanGreenColor), onClick: () { Navigator.pushNamed(context, '/CoachPaymentPlan'); }, ), const SizedBox(height: 10), CustomButtonWidget( width: MediaQuery.of(context).size.width, height: 36.0, title: notNowTxt, border: null, titleColor: whiteColor, fontSize: Dimensions.fontSizeLarge, borderRadius: BorderRadius.circular(6.0), backgroundColor: HexColor(americanGreenColor), onClick: () { Navigator.pop(context); }, ), ], ), ), ), ); } } else { await saveFreeUserPopUpDate(''); } await callIsAddedWeightFuture(true); } }

Yes, you can safely change the inDays check from 7 to 1 in your code. Here's the modified version with that change:

This change means:

  1. The popup will now show to free users once per day (instead of once per week)
  2. The logic remains exactly the same, only the frequency threshold has changed
  3. All other functionality (saving the last shown date, checking subscription status, etc.) remains unchanged

The change is safe to implement as it only affects how often the popup appears, not any core functionality.

Updated files
v3