maxim-saplin / data_table_2

In-place substitute for Flutter's DataTable and PaginatedDataTable with fixed/sticky header and extra features
https://pub.dev/packages/data_table_2
BSD 3-Clause "New" or "Revised" License
203 stars 140 forks source link

Update DataCell once it loaded when using AsyncDataTableSource #299

Open devrndsvits opened 2 months ago

devrndsvits commented 2 months ago

Thanks a lot for amazing library @maxim-saplin

I'm using AsyncDataTableSource with dummy api and it works perfectly fine but I have requirement when user select a row I want to show icon on first column which is fixed column. Icon represent the indication that row is selected.

I'm not able find any event that update cell or refresh the cell when row is tap. Below is minimal production code thus you can have a look it. I tried notifyListeners but still no luck.

Any suggestion and help is highly appreciated.

UserAsyncScreen

import 'package:data_table_2/data_table_2.dart';
import 'package:datatable_paging/user_model/user.dart';
import 'package:datatable_paging/using_lib/user_async_data_source.dart';
import 'package:flutter/material.dart';

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

  @override
  State<UserAsyncScreen> createState() => _UserAsyncScreenState();
}

class _UserAsyncScreenState extends State<UserAsyncScreen> {
  int _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
  bool _sortAscending = true;
  int? _sortColumnIndex;
  UserAsyncDataSource? _userAsyncDataSource;
  final PaginatorController _controller = PaginatorController();

  bool _dataSourceLoading = false;
  int _initialRow = 0;

  bool isColumnAutoWidth = true;

  User? _selectedUser;
  @override
  void initState() {
    _userAsyncDataSource = UserAsyncDataSource(
      isColumnAutoWidth: isColumnAutoWidth,
      onRowSelected: (User selectedUser) {
        setState(() {
          _selectedUser = selectedUser; // Update selected user
          print(_selectedUser?.firstname.toString() ?? "");
        });
      },
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    if (_dataSourceLoading) return const SizedBox();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Data Table'),
      ),
      body: Column(
        children: [
          Expanded(
            child: AsyncPaginatedDataTable2(
                headingRowColor: WidgetStateColor.resolveWith(
                    (states) => Colors.purple[900]!),
                headingTextStyle: const TextStyle(color: Colors.white),
                horizontalMargin: 20,
                checkboxHorizontalMargin: 12,
                columnSpacing: 20,
                wrapInCard: false,
                rowsPerPage: _rowsPerPage,
                autoRowsToHeight: false,
                pageSyncApproach: PageSyncApproach.doNothing,
                showCheckboxColumn: false,
                minWidth:
                    isColumnAutoWidth ? MediaQuery.sizeOf(context).width : 2500,
                fit: FlexFit.tight,
                border: TableBorder(
                    top: const BorderSide(color: Colors.black),
                    bottom: BorderSide(color: Colors.grey[300]!),
                    left: BorderSide(color: Colors.grey[300]!),
                    right: BorderSide(color: Colors.grey[300]!),
                    verticalInside: BorderSide(color: Colors.grey[300]!),
                    horizontalInside:
                        const BorderSide(color: Colors.grey, width: 1)),
                availableRowsPerPage: const [5, 10, 15, 20],
                onRowsPerPageChanged: (value) {
                  _rowsPerPage = value!;
                },
                initialFirstRowIndex: _initialRow,
                onPageChanged: (rowIndex) {},
                sortColumnIndex: _sortColumnIndex,
                sortAscending: _sortAscending,
                sortArrowIcon: Icons.keyboard_arrow_up,
                sortArrowAnimationDuration: const Duration(milliseconds: 500),
                sortArrowBuilder: (ascending, sorted) {
                  if (sorted) {
                    if (ascending) {
                      return const Padding(
                        padding: EdgeInsets.only(left: 20.0),
                        child: Icon(
                          Icons.keyboard_arrow_down,
                          color: Colors.white,
                        ),
                      );
                    } else {
                      return const Padding(
                        padding: EdgeInsets.only(left: 20.0),
                        child: Icon(
                          Icons.keyboard_arrow_up,
                          color: Colors.white,
                        ),
                      );
                    }
                  } else {
                    return const SizedBox();
                  }
                },
                controller: _controller,
                hidePaginator: false,
                fixedLeftColumns: isColumnAutoWidth ? 1 : 1,
                fixedColumnsColor: Colors.purple[900],
                columns: [
                  const DataColumn2(label: Text(' '), fixedWidth: 30.0),
                  DataColumn2(
                    label: const Text('Id'),
                    onSort: (columnIndex, ascending) =>
                        sort(columnIndex, ascending),
                  ),
                  DataColumn2(
                    label: const Text('Firstname'),
                    onSort: (columnIndex, ascending) =>
                        sort(columnIndex, ascending),
                  ),
                  DataColumn2(
                    label: const Text('Lastname'),
                    onSort: (columnIndex, ascending) =>
                        sort(columnIndex, ascending),
                  ),
                ],
                empty: Center(
                  child: Container(
                    padding: const EdgeInsets.all(20),
                    color: Colors.grey[200],
                    child: const Text('No data'),
                  ),
                ),
                source: _userAsyncDataSource!),
          ),
        ],
      ),
    );
  }

  void sort(
    int columnIndex,
    bool ascending,
  ) {
    var columnName = "id";
    switch (columnIndex) {
      case 0:
        columnName = "id";
        break;
      case 1:
        columnName = "id";
        break;
      case 2:
        columnName = "Firstname";
        break;
      case 3:
        columnName = "Lastname";
        break;
    }
    _userAsyncDataSource!.sort(columnName, ascending);
    setState(() {
      _sortColumnIndex = columnIndex;
      _sortAscending = ascending;
    });
  }
}

UserAsyncDataSource

import 'package:data_table_2/data_table_2.dart';
import 'package:datatable_paging/api_manager/api_manager.dart';
import 'package:datatable_paging/user_model/user.dart';
import 'package:flutter/material.dart';

class UserAsyncDataSource extends AsyncDataTableSource {
  final APIManager apiManager = APIManager();

  final bool isColumnAutoWidth;
  final void Function(User)? onRowSelected;

  UserAsyncDataSource({this.isColumnAutoWidth = true, this.onRowSelected});

  String _sortColumn = "id";
  bool _sortAscending = true;

  User? selectedUser; // Currently selected user

  bool showSelection = false;

  var appColor = Colors.white;

  @override
  Future<AsyncRowsResponse> getRows(int startIndex, int count) async {
    int apiPage = ((startIndex / count) + 1).toInt();

    final List<User> newData = await apiManager.getUserDetailsPage(
      page: apiPage,
      limit: count,
      sortBy: _sortColumn,
      order: _sortAscending ? "asc" : "desc",
    );

    var r = AsyncRowsResponse(
        100,
        newData.map((user) {
          int index = newData.indexOf(user);
          return DataRow2(
            color: WidgetStateProperty.resolveWith<Color>((states) {
              if (states.contains(WidgetState.selected)) {
                return Colors.purple[50]!;
              }
              return Colors.transparent;
            }),
            key: ValueKey<int>(index),
            onSelectChanged: (selected) {
              if (selected != null) {
                appColor = Colors.amber;
                deselectAll();
                setRowSelection(ValueKey<int>(index), selected);
                onRowSelected?.call(user); // Notify when a row is selected
                selectedUser = user;
                notifyListeners();
              }
            },
            cells: [
              DataCell(
                Transform.translate(
                  offset: const Offset(-8.0, 0),
                  child: Icon(
                    Icons.play_arrow_rounded,
                    color: selectedUser == user
                        ? Colors.white
                        : Colors.transparent,
                  ),
                ),
              ),
              DataCell(Text(user.id)),
              DataCell(Text(user.firstname)),
              DataCell(Text(user.lastname)),
            ],
          );
        }).toList());

    return r;
  }

  Future<int> getTotalRecords() {
    return Future<int>.delayed(const Duration(milliseconds: 0), () => 100);
  }

  void sort(String columnName, bool ascending) {
    deselectAll();
    selectedUser = null;
    _sortColumn = columnName;
    _sortAscending = ascending;
    refreshDatasource();
  }

  @override
  void notifyListeners() {
    if (showSelection) {
      appColor = Colors.amber;
      showSelection = false;
    }
    super.notifyListeners();
  }
}

APIManager

import 'package:datatable_paging/user_model/user.dart';
import 'package:dio/dio.dart';

class APIManager {
  final Dio dioForUser = Dio();

  Future<List<User>> getUserDetailsPage(
      {required int page,
      required int limit,
      required String sortBy,
      required String order}) async {
    try {
      final response = await dioForUser.get(
        'https://66c6b5538b2c10445bc76efa.mockapi.io/api/v1/getUserDetails',
        queryParameters: {
          'page': page,
          'limit': limit,
          'sortBy': sortBy,
          'order': order
        },
      );
      if (response.statusCode == 200) {
        List<dynamic> data = response.data;
        return data.map((json) => User.fromJson(json)).toList();
      } else {
        throw Exception('Failed to load user details');
      }
    } catch (e) {
      throw Exception('Error fetching data: $e');
    }
  }
}
maxim-saplin commented 2 months ago

@devrndsvits - if you would like to update the column title - the best you can do is refresh the whole widget. If you want to change the row - just update the datasource. Sorry, no capacity to look in your code...

NourEldin-Ali commented 1 month ago

Hi @maxim-saplin

Thank you for this plugin.

I have the same error for the notifyListeners in AsyncDataTableSource. I does not work well.

For example I added edit button at the end of row, and after click edit the Text the Text('test') should be modified to 'TextFormField(...)' but it does not changed (Note: I was tried before in other plugin it worked well, but it seems to me that the error in the notifyListeners).

Also, if i put in the DataRow( selected: true,...) the the checkbox is not selected.