Menus

Monday, 31 March 2025

Display grouped data in a Flutter table with totals at each grouping level, including a grand total.

 

📌 Steps to Achieve:

  1. Store data in a list (with category, sub_category, item, and amount).

  2. Group data by category and sub_category.

  3. Calculate totals for each sub_category and category.

  4. Compute the grand total.

  5. Display everything in a table format.


📌 Expected Output:


CategorySub-CategoryItemAmount
FruitsCitrusOrange10
Lemon5
Subtotal15
BerriesStrawberry8
Blueberry12
Subtotal20
Total for Fruits35
VegetablesLeafySpinach7
Lettuce6
Subtotal13
RootCarrot9
Potato11
Subtotal20
Total for Vegetables33
Grand Total68



🔹 Example Implementation

dart


import 'package:flutter/material.dart'; class GroupedTableScreen extends StatefulWidget { @override _GroupedTableScreenState createState() => _GroupedTableScreenState(); } class _GroupedTableScreenState extends State<GroupedTableScreen> { // Sample data: category, sub_category, item, amount final List<Map<String, dynamic>> items = [ {'category': 'Fruits', 'sub_category': 'Citrus', 'item': 'Orange', 'amount': 10}, {'category': 'Fruits', 'sub_category': 'Citrus', 'item': 'Lemon', 'amount': 5}, {'category': 'Fruits', 'sub_category': 'Berries', 'item': 'Strawberry', 'amount': 8}, {'category': 'Fruits', 'sub_category': 'Berries', 'item': 'Blueberry', 'amount': 12}, {'category': 'Vegetables', 'sub_category': 'Leafy', 'item': 'Spinach', 'amount': 7}, {'category': 'Vegetables', 'sub_category': 'Leafy', 'item': 'Lettuce', 'amount': 6}, {'category': 'Vegetables', 'sub_category': 'Root', 'item': 'Carrot', 'amount': 9}, {'category': 'Vegetables', 'sub_category': 'Root', 'item': 'Potato', 'amount': 11}, ]; // Method to group and calculate totals Map<String, Map<String, dynamic>> groupData() { Map<String, Map<String, dynamic>> groupedData = {}; for (var item in items) { String category = item['category']; String subCategory = item['sub_category']; int amount = item['amount']; // Initialize category if (!groupedData.containsKey(category)) { groupedData[category] = {'sub_categories': {}, 'total': 0}; } // Initialize sub-category if (!groupedData[category]!['sub_categories'].containsKey(subCategory)) { groupedData[category]!['sub_categories'][subCategory] = {'items': [], 'total': 0}; } // Add item to sub-category groupedData[category]!['sub_categories'][subCategory]['items'].add(item); groupedData[category]!['sub_categories'][subCategory]['total'] += amount; groupedData[category]!['total'] += amount; // Update category total } return groupedData; } @override Widget build(BuildContext context) { Map<String, Map<String, dynamic>> groupedData = groupData(); int grandTotal = groupedData.values.fold(0, (sum, cat) => sum + (cat['total'] as num).toInt()); return Scaffold( appBar: AppBar(title: Text('Grouped Data Table')), body: SingleChildScrollView( scrollDirection: Axis.horizontal, child: DataTable( columns: [ DataColumn(label: Text('Category')), DataColumn(label: Text('Sub-Category')), DataColumn(label: Text('Item')), DataColumn(label: Text('Amount')), ], rows: _buildRows(groupedData, grandTotal), ), ), ); } // Method to build rows dynamically List<DataRow> _buildRows(Map<String, Map<String, dynamic>> groupedData, int grandTotal) { List<DataRow> rows = []; groupedData.forEach((category, catData) { bool firstCategoryRow = true; catData['sub_categories'].forEach((subCategory, subData) { bool firstSubCategoryRow = true; for (var item in subData['items']) { rows.add(DataRow(cells: [ DataCell(firstCategoryRow ? Text(category, style: TextStyle(fontWeight: FontWeight.bold)) : Text('')), DataCell(firstSubCategoryRow ? Text(subCategory, style: TextStyle(fontWeight: FontWeight.w600)) : Text('')), DataCell(Text(item['item'])), DataCell(Text('${item['amount']}')), ])); firstCategoryRow = false; firstSubCategoryRow = false; } // Sub-category total row rows.add(DataRow(cells: [ DataCell(Text('')), DataCell(Text('Subtotal:', style: TextStyle(fontWeight: FontWeight.bold))), DataCell(Text('')), DataCell(Text('${subData['total']}', style: TextStyle(fontWeight: FontWeight.bold))), ])); }); // Category total row rows.add(DataRow(cells: [ DataCell(Text('Total for $category', style: TextStyle(fontWeight: FontWeight.bold))), DataCell(Text('')), DataCell(Text('')), DataCell(Text('${catData['total']}', style: TextStyle(fontWeight: FontWeight.bold))), ])); }); // Grand total row rows.add(DataRow(cells: [ DataCell(Text('Grand Total', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))), DataCell(Text('')), DataCell(Text('')), DataCell(Text('$grandTotal', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16))), ])); return rows; } }




Friday, 21 February 2025

Flutter : To avoid repeated loading of dropdown items

 

To avoid repeated loading of dropdown items (like courses, universities, etc.) on every page, you can use caching or a singleton pattern to load the items once and reuse them throughout your app. Here are the best solutions:

Solution 1: Use a Global Cache (Shared State)

You can store the dropdown items in a singleton service class or global variable and load them only once.

Step 1: Create a Singleton Service

dart
class DropdownService { static final DropdownService _instance = DropdownService._internal(); factory DropdownService() { return _instance; } DropdownService._internal(); List<Map<String, String>> courseItems = []; List<Map<String, String>> universityItems = []; bool isLoaded = false; Future<void> loadDropdowns() async { if (!isLoaded) { // Load only if not loaded before courseItems = await fetchGroupitems("2"); // Load Courses universityItems = await fetchGroupitems("3"); // Load Universities isLoaded = true; } } }


Step 2: Load Data Once in main.dart

dart
void main() async { WidgetsFlutterBinding.ensureInitialized(); await DropdownService().loadDropdowns(); // Load only once runApp(MyApp()); }

Step 3: Use in Any Page Without Reloading

dart
class MyDropdownPage extends StatefulWidget { @override _MyDropdownPageState createState() => _MyDropdownPageState(); } class _MyDropdownPageState extends State<MyDropdownPage> { List<Map<String, String>> courseItems = DropdownService().courseItems; String selectedCourse = "0"; @override Widget build(BuildContext context) { return DropdownButtonFormField<String>( decoration: InputDecoration(labelText: "Select Course"), value: selectedCourse, items: courseItems.map((item) { return DropdownMenuItem<String>( value: item['id'], child: Text(item['name'] ?? ""), ); }).toList(), onChanged: (value) { setState(() { selectedCourse = value!; }); }, ); } }


Solution 2: Store Dropdown Data in SharedPreferences

You can store the dropdown values in SharedPreferences and only update them when needed.

Step 1: Save Data After Fetching

dart
import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; Future<void> saveDropdownData(String key, List<Map<String, String>> data) async { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setString(key, jsonEncode(data)); }

Step 2: Load Data from Cache

dart
Future<List<Map<String, String>>> loadDropdownData(String key) async { SharedPreferences prefs = await SharedPreferences.getInstance(); String? data = prefs.getString(key); if (data != null) { return List<Map<String, String>>.from(jsonDecode(data)); } return []; }

Step 3: Fetch Data Only if Needed

dart
Future<void> fetchCourses() async { courseItems = await loadDropdownData("courses"); if (courseItems.isEmpty) { courseItems = await fetchGroupitems("2"); saveDropdownData("courses", courseItems); } }

This way, data is fetched from cache instead of calling the API repeatedly.


Solution 3: Use a Provider or Riverpod for State Management

If you're using Provider or Riverpod, you can store the dropdown values in the app state and access them without reloading.

Using Provider

dart
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class DropdownProvider with ChangeNotifier { List<Map<String, String>> courseItems = []; Future<void> loadDropdowns() async { if (courseItems.isEmpty) { courseItems = await fetchGroupitems("2"); notifyListeners(); // Notify UI to update } } }

Wrap Your App with Provider

dart
void main() { runApp( ChangeNotifierProvider( create: (_) => DropdownProvider()..loadDropdowns(), child: MyApp(), ), ); }

Use in Any Page

dart
DropdownButtonFormField<String>( value: selectedCourse, items: Provider.of<DropdownProvider>(context).courseItems.map((item) { return DropdownMenuItem<String>( value: item['id'], child: Text(item['name']), ); }).toList(), onChanged: (value) { setState(() { selectedCourse = value!; }); }, );

Which Solution Should You Use?

SolutionWhen to Use?
Singleton (DropdownService)If dropdown data is needed across multiple pages and should only be loaded once
SharedPreferences CacheIf you want dropdown data to persist even after the app restarts
Provider/RiverpodIf you're already using state management for better efficiency