smart_data_table_plus 1.0.0 copy "smart_data_table_plus: ^1.0.0" to clipboard
smart_data_table_plus: ^1.0.0 copied to clipboard

A customizable responsive data table for Flutter with pagination, sorting, row selection, and adaptive layouts.

example/lib/main.dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:smart_data_table_plus/smart_data_table_plus.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String selectedValue = '10';
  final List<String> items = ['5', '10', '20'];
  int _currentPage = 1;

  bool _isLoading = false;
  bool _showEmpty = false;

  Set<Product> _selectedProducts = {};

  final List<Product> _allProducts = List.generate(100, (i) {
    final names = [
      "Japanese cherry blossom body butter",
      "Ultra shea body butter",
      "Wheat Vitamin E body butter",
      "Strawberry body butter",
      "Moringa body butter",
      "Pink grapefruit body butter",
      "Strawberry hand cream",
      "Mango hand cream",
      "Vanilla Pumpkin Hand Cream",
    ];
    final colors = [
      Colors.pink.shade100,
      Colors.brown.shade200,
      Colors.yellow.shade200,
      Colors.red.shade200,
      Colors.green.shade200,
      Colors.orange.shade200,
    ];
    final prices = [35.0, 35.0, 35.0, 35.0, 30.0, 30.0, 25.0, 25.0, 25.0];
    final costs = [24.0, 24.0, 24.0, 24.0, 17.0, 17.0, 17.0, 17.0, 17.0];
    final idx = i % names.length;
    final rand = Random(i);
    return Product(
      id: "P${1000 + i}",
      name: names[idx],
      category: "Skin care",
      sku: "SK-${1000 + rand.nextInt(9000)}",
      quantitySold: 40 + rand.nextInt(60),
      quantityTotal: 100,
      cost: costs[idx],
      price: prices[idx],
      status: ["Active", "Active", "Active", "Inactive", "Draft"][
      rand.nextInt(5)],
      color: colors[idx % colors.length],
    );
  });

  List<Product> get products => _showEmpty ? [] : _allProducts;

  int get perPages => int.parse(selectedValue);
  int get totalPages => max(1, (products.length / perPages).ceil());

  @override
  Widget build(BuildContext context) {
    final start = (_currentPage - 1) * perPages;
    final end = min(start + perPages, products.length);

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(
          title: const Text("Smart Data Table Plus Example"),
          actions: [
            Row(
              children: [
                const Text("Loading"),
                Switch(
                  value: _isLoading,
                  onChanged: (v) => setState(() => _isLoading = v),
                ),
                const SizedBox(width: 12),
                const Text("Empty"),
                Switch(
                  value: _showEmpty,
                  onChanged: (v) => setState(() => _showEmpty = v),
                ),
                const SizedBox(width: 12),
              ],
            ),
          ],
        ),
        body: Container(
          color: Colors.white,
          padding: const EdgeInsets.all(20),
          child: Column(
            children: [
              // ── Table ──
              Expanded(
                child: SmartDataTablePlus<Product>(
                  titleHeader: const [
                    "Product name",
                    "Category",
                    "SKU",
                    "Quantity",
                    "Cost",
                    "Price",
                    "Status",
                    "Actions",
                  ],
                  showCheckbox: true,
                  showHover: true,
                  selectedItems: _selectedProducts,
                  onSelectionChanged: (selected) {
                    setState(() => _selectedProducts = selected.toSet());
                  },
                  checkboxBuilder: (product, isSelected, onChanged) {
                    return GestureDetector(
                      onTap: () => onChanged(!isSelected),
                      child: AnimatedContainer(
                        duration: const Duration(milliseconds: 120),
                        width: 20,
                        height: 20,
                        decoration: BoxDecoration(
                          color: isSelected
                              ? Colors.blueAccent
                              : Colors.transparent,
                          border: Border.all(
                            color: isSelected
                                ? Colors.blueAccent
                                : Colors.grey.shade400,
                            width: 1.5,
                          ),
                          borderRadius: BorderRadius.circular(5),
                        ),
                        child: isSelected
                            ? const Icon(
                          Icons.check,
                          size: 14,
                          color: Colors.white,
                        )
                            : null,
                      ),
                    );
                  },
                  headerCheckboxBuilder: (value, onChanged) {
                    final isIndeterminate = value == null;
                    final isChecked = value == true;
                    return GestureDetector(
                      onTap: onChanged == null
                          ? null
                          : () => onChanged(!isChecked),
                      child: Container(
                        width: 20,
                        height: 20,
                        decoration: BoxDecoration(
                          color: (isChecked || isIndeterminate)
                              ? Colors.blueAccent
                              : Colors.transparent,
                          border: Border.all(
                            color: (isChecked || isIndeterminate)
                                ? Colors.blueAccent
                                : Colors.grey.shade400,
                            width: 1.5,
                          ),
                          borderRadius: BorderRadius.circular(5),
                        ),
                        child: isIndeterminate
                            ? const Icon(Icons.remove,
                            size: 14, color: Colors.white)
                            : (isChecked
                            ? const Icon(Icons.check,
                            size: 14, color: Colors.white)
                            : null),
                      ),
                    );
                  },
                  isLoading: _isLoading,
                  headerBackgroundColor: Colors.grey.shade50,
                  evenRowColor: Colors.white,
                  oddRowColor: Colors.grey.withAlpha(30),
                  headerStyle: const TextStyle(
                    color: Colors.black87,
                    fontSize: 13,
                    fontWeight: FontWeight.w600,
                  ),
                  data: products,
                  page: _currentPage,
                  perPages: perPages,
                  borderRadius: 10,
                  borderColor: Colors.grey.shade300,
                  rowHoverColor: Colors.grey.withAlpha(40),
                  empty: Center(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(
                          Icons.inventory_2_outlined,
                          size: 48,
                          color: Colors.grey.shade400,
                        ),
                        const SizedBox(height: 12),
                        Text(
                          "No products yet",
                          style: TextStyle(
                            fontSize: 14,
                            fontWeight: FontWeight.w600,
                            color: Colors.grey.shade700,
                          ),
                        ),
                        const SizedBox(height: 4),
                        Text(
                          "Products you add will show up here.",
                          style: TextStyle(
                            fontSize: 13,
                            color: Colors.grey.shade500,
                          ),
                        ),
                        const SizedBox(height: 16),
                        OutlinedButton.icon(
                          onPressed: () {
                            setState(() => _showEmpty = false);
                          },
                          icon: const Icon(Icons.add, size: 18),
                          label: const Text("Add product"),
                        ),
                      ],
                    ),
                  ),
                  columnFlex: const [
                    2, // Product name
                    1, // Category
                    1, // SKU
                    1, // Quantity
                    1, // Cost
                    1, // Price
                    1, // Status
                    1, // Actions
                  ],
                  listItem: (p) => [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: [
                        CircleAvatar(
                          radius: 16,
                          backgroundColor: p.color,
                        ),
                        const SizedBox(width: 12),
                        Expanded(
                          child: Text(
                            p.name,
                            overflow: TextOverflow.ellipsis,
                            style: const TextStyle(
                              fontSize: 13,
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                        ),
                      ],
                    ),
                    Text(p.category,
                        style: TextStyle(color: Colors.grey.shade700)),
                    Text(p.sku, style: TextStyle(color: Colors.grey.shade700)),
                    Text("${p.quantitySold}/${p.quantityTotal}"),
                    Text("\$${p.cost.toStringAsFixed(2)}"),
                    Text("\$${p.price.toStringAsFixed(2)}"),
                    buildStatus(p.status),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        IconButton(
                          icon: const Icon(Icons.edit_outlined, size: 18),
                          onPressed: () {},
                        ),
                        IconButton(
                          icon: const Icon(Icons.more_vert, size: 18),
                          onPressed: () {},
                        ),
                      ],
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 12),

              /// ── Pagination ──
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Row(
                    children: [
                      const Text("Rows per page: "),
                      const SizedBox(width: 8),
                      DropdownButton<String>(
                        value: selectedValue,
                        items: items
                            .map((v) =>
                            DropdownMenuItem(value: v, child: Text(v)))
                            .toList(),
                        onChanged: (value) {
                          if (value == null) return;
                          setState(() {
                            selectedValue = value;
                            _currentPage = 1;
                          });
                        },
                      ),
                    ],
                  ),
                  Row(
                    children: [
                      Text(
                        products.isEmpty
                            ? "0 of 0"
                            : "${start + 1}-$end of ${products.length}",
                      ),
                      IconButton(
                        icon: const Icon(Icons.chevron_left),
                        onPressed: _currentPage > 1
                            ? () => setState(() => _currentPage--)
                            : null,
                      ),
                      Text("Page $_currentPage of $totalPages"),
                      IconButton(
                        icon: const Icon(Icons.chevron_right),
                        onPressed: _currentPage < totalPages
                            ? () => setState(() => _currentPage++)
                            : null,
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget buildStatus(String status) {
    Color bg;
    Color text;

    switch (status) {
      case "Active":
        bg = Colors.green.shade100;
        text = Colors.green.shade800;
        break;
      case "Inactive":
        bg = Colors.red.shade100;
        text = Colors.red.shade800;
        break;
      default:
        bg = Colors.orange.shade100;
        text = Colors.orange.shade800;
    }

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
      decoration: BoxDecoration(
        color: bg,
        borderRadius: BorderRadius.circular(30),
      ),
      child: Text(
        status,
        textAlign: TextAlign.center,
        style: TextStyle(
          color: text,
          fontWeight: FontWeight.w600,
          fontSize: 12,
        ),
      ),
    );
  }
}

class Product {
  final String id;
  final String name;
  final String category;
  final String sku;
  final int quantitySold;
  final int quantityTotal;
  final double cost;
  final double price;
  final String status;
  final Color color;

  const Product({
    required this.id,
    required this.name,
    required this.category,
    required this.sku,
    required this.quantitySold,
    required this.quantityTotal,
    required this.cost,
    required this.price,
    required this.status,
    required this.color,
  });
}
3
likes
160
points
73
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A customizable responsive data table for Flutter with pagination, sorting, row selection, and adaptive layouts.

Repository (GitHub)
View/report issues

Topics

#table #datatable #pagination #responsive #flutter

License

MIT (license)

Dependencies

flutter

More

Packages that depend on smart_data_table_plus