Flutter: Is there a way to build a complex widget asynchronously without blocking main isolate?

Viewed 344

I want to display a DataTable where the data input varies based on the user's input (the user can pick a csv file and its content will then be displayed in the DataTable widget).

My problem is that the DataTable creation can take a few seconds depending on the number of rows and columns in the user's csv file and the screen freezes in the meantime, which is obviously a bad user experience. Therefore, my idea was to create the DataTable in a separate isolate, await the result (see code below) and show a CircularProgressIndicator while waiting for the result.

Unfortunately, this approach is not working (I assume because CustomDataTable is not a valid message type).

My approach using isolates here might be wrong (I am fairly new to flutter) and I am happy to switch to a different strategy if one can guide me in the right direction.

// In the class of my Stateful Widget that will display the CustomDataTable I have this function spawning the isolate and awaiting the CustomDataTable
Future<CustomDataTable> createCustomTableInIsolate(List<Map<String, String>> data, List<Map<String, String>> selectedRows, List<String> columnNames, Function onSelectedRow) async {
  var map = {
    'data': data,
    'selectedRows': selectedRows,
    'columnNames': columnNames,
    'onSelectedRow': onSelectedRow,
  };

  CustomDataTable dataTable = await compute(createCustomTable, map);
  return dataTable;
}

//... somewhere in my widget tree
FutureBuilder<CustomDataTable>(
    future: createCustomTableInIsolate(),
    builder: (BuildContext ctx,
        AsyncSnapshot snapshot) {
      if (snapshot.connectionState ==
          ConnectionState.waiting) {
        return Center(
              child: CircularProgressIndicator(),
            );
      } else if (!snapshot.hasData) {
        return Center(
              child: Text('Please check your input data...'),
            );
      } else {
        return SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: ConstrainedBox(
              constraints: BoxConstraints(
                minWidth: constraints.minWidth,
              ),
              child: snapshot.data),
        );
      }
    },
  ),
// ... 

// Function creating the CustomDataTable in different isolate (outside of stateful widget class)
CustomDataTable createCustomTable(Map<String, dynamic> map) {
  return CustomDataTable(
    data: map['data'],
    selectedRows: map['selectedRows'],
    columnNames: map['columnNames'],
    onSelectedRow: map['onSelectedRow'],
  );
}

// Separate class to create the DataTable widget
class CustomDataTable extends StatelessWidget {
  final List<Map<String, String>> data;
  final List<Map<String, String>> selectedRows;
  final List<String> columnNames;
  final Function onSelectedRow;

  CustomDataTable({
    @required this.data,
    @required this.selectedRows,
    @required this.columnNames,
    @required this.onSelectedRow,
  });

  @override
  Widget build(BuildContext context) {
    return DataTable(
      columns: columnNames.map((columnName) {
        return DataColumn(
          label: Text(
            columnName,
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.w600,
            ),
          ),
        );
      }).toList(),
      rows: data.map((column) {
        var columnValues = column.values;
        return DataRow(
            selected: selectedRows.contains(column),
            onSelectChanged: (selected) {
              onSelectedRow(selected, column);
            },
            cells: columnValues.map((cellValue) {
              return DataCell(
                Text(
                  cellValue,
                  style: TextStyle(
                    color: Colors.white,
                  ),
                ),
              );
            }).toList());
      }).toList(),
    );
  }
}
0 Answers
Related