Flutter:使用来自 Firestore 的多集合流创建动态更新的 DataTable

Posted

技术标签:

【中文标题】Flutter:使用来自 Firestore 的多集合流创建动态更新的 DataTable【英文标题】:Flutter: Creating a dynamically updating DataTable with multi-collection stream from Firestore 【发布时间】:2020-06-01 18:13:26 【问题描述】:

所以我(试图)创建一个 DataTable 以根据 Firestore 数据动态显示一堆包裹(如亚马逊或 UPS 包裹)。复杂之处在于:我的数据存储在数据库中的多个集合中,即“队列”、“内部”和“学生”。

我使用 ListView.builder 和 ListViewTiles 创建了我想要的版本。目前它运行良好,在将新数据添加到队列时显示新图块。我不想使用 ListView,而是想在一个表(DataTable)中显示所有数据;但是,我无法以与 DataTable 一起使用的方式适应我已有的(FutureBuilders 和 StreamBuilders)。

我还有一个占位符 DataTable,它以我想要的确切方式显示来自预定义列表的一堆虚拟包。

我已经研究过使用 Provider 为 DataTable 小部件提供重建列表...但是当我尝试同时从多个集合中读取时,我似乎无法连接这些点。

这是我的 ListView 的代码...

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

class QueueDisplay extends StatefulWidget 
  @override
  _QueueDisplayState createState() => _QueueDisplayState();


class _QueueDisplayState extends State<QueueDisplay> 
  @override
  Widget build(BuildContext context) 
    return Container(
      child: StreamBuilder(
        stream: Firestore.instance.collection('queue').snapshots(),
        builder:(context, snapshot)
          if (!snapshot.hasData)
            return (Center(child: CircularProgressIndicator()));
           else 
            return ListView.builder(
              itemCount: snapshot.data.documents.length,
              itemBuilder: (context, index) 
                DocumentSnapshot queueDoc = snapshot.data.documents[index];
                return Column(
                  children: <Widget>[
                    FutureBuilder(
                      future: Firestore.instance.collection('students').document('$queueDoc.data['owner']').get(),
                      builder: (BuildContext context, AsyncSnapshot studentSnap) 
                        return FutureBuilder(
                          future: Firestore.instance.collection('inhouse').where('owner', isEqualTo: queueDoc.data['owner']).getDocuments(),
                          builder: (BuildContext context, AsyncSnapshot packSnap) 
                            if (packSnap.hasData)
                              if(packSnap.data!=null)
                                return ListTile(
                                    title: Row(
                                      children: <Widget>[
                                        Text('$queueDoc.data['owner']' + '     '),
                                        Text('$studentSnap.data['name']' + '     '),
                                        Text('$packSnap.data.documents[0].documentID'),
                                      ],
                                    )
                                );
                              
                             else 
                              return Center(child: CircularProgressIndicator());
                            
                            return Center(child: CircularProgressIndicator());
                          ,
                        );
                      ,
                    )
                  ],
                );
              ,
            );
          
        ,
      ),
    );
  

Functioning ListView Screenshot

这是占位符数据表的代码...

class QueueTable extends StatefulWidget 
  @override
  _QueueTableState createState() => _QueueTableState();


class _QueueTableState extends State<QueueTable> 
  @override
  Widget build(BuildContext context) 
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: Container(
        //color: Colors.grey[200],
        child: DataTable(
          // Table Displaying Queued Packages
            columnSpacing: 8,
            columns: [
              DataColumn(label: Text("Name"),numeric: false, onSort: (i, b) ),
              DataColumn(label: Text("Location"),numeric: false, onSort: (i, b) ),
              DataColumn(label: Text("Tracking"),numeric: false, onSort: (i, b) ),
              DataColumn(label: Text("Carrier"),numeric: false, onSort: (i, b) ),
              DataColumn(label: Text("Notes"),numeric: false, onSort: (i, b) ),
              DataColumn(label: Text("Received"),numeric: false, onSort: (i, b) ),
            ],
            rows: packages.map((package) => DataRow(
                onSelectChanged: (b) ,
                cells: [
                  DataCell(Text(package.owner.name), onTap: ()),
                  DataCell(Text(package.location), onTap: ()),
                  DataCell(Text(package.tracking)),
                  DataCell(Text(package.carrier)),
                  DataCell(Text(package.notes), onTap: ()),
                  DataCell(Text(package.rDate)),
            ])).toList()
        ),
      ),
    );
  


Student adam =
    Student('0000004028', 'Adam G', 'adamg@email.net');
var packages = [
  Package(adam, 'Large', 'USPS', '1/15/2020', '123456', 'Great Dude!'),
  Package(adam, 'Small', 'UPS', '1/16/2020', '1234567', 'Great Guy!'),
  Package(adam, 'Medium', 'FedEx', '1/18/2020', '12345678', 'Great Bro!'),
  Package(adam, 'Floor', 'Amazon', '1/5/2020', '123456789', 'Great Person!'),
];

Placeholder DataTable Screenshot

提供的两段代码都应该可以正常工作;我似乎无法将它们组合成一个有效的动态数据表......

有什么建议吗?谢谢!

更新:

所以这是我迄今为止最接近的尝试......

我制作了一个 List 流,并使用 StreamProvider 将流提供给创建 DataTable 的小部件。

database.dart

Provider that only returns null Packages

似乎它起作用;如果我能让 _queueListFromSnapshot 为 Package p 返回正确的值。由于嵌套的 Future.then() 语句,我还不知道如何返回正确的值。 (在database.dart的cmets中显示)

更新 2:

这是一个使用 async/await 的 slightly better try。这需要我更改导致不匹配的函数的返回类型,因为我仍然想将完整的列表返回给 StreamProvider... 对吗?

【问题讨论】:

【参考方案1】:
            import 'dart:io';
            import 'package:flutter/material.dart';
            import 'package:flutter/rendering.dart';
            import 'package:test4/sqlite/Sqlite.dart';

            class DataColumnEx 
              bool show;
              String label;
              String tooltip;
              bool numeric;
              bool editable;
              TextInputType editableTextInputType = TextInputType.text;
              DataColumnEx(
                  @required this.show,
                  this.label = "",
                  this.tooltip = "",
                  this.numeric = false,
                  this.editable = false,
                  this.editableTextInputType = TextInputType.text);
            

            class DataTableWidget extends StatefulWidget 
              final String dbName;
              final String sqlScript;
              final Map<String, DataColumnEx> columns;
              DataTableWidget(
                  @required this.dbName,
                  @required this.sqlScript,
                  @required this.columns);

              @override
              _DataTableWidgetState createState() => _DataTableWidgetState();
            

            class _DataTableWidgetState extends State<DataTableWidget> 
              bool _sortAscending = true;
              int _sortColumnIndex = 0;
              List<Map<String, dynamic>> _snapshot;

              List<Map<String, dynamic>> _getsnapshot() 
                return _snapshot;
              

              _setsnapshot(AsyncSnapshot<List<Map<String, dynamic>>> snapshot) 
                _snapshot = List<Map<String, dynamic>>.from(snapshot.data);
              

              _sort() 
                String columnName = _getsnapshot().first.keys.elementAt(_sortColumnIndex);
                if (_sortAscending)
                  _getsnapshot().sort((a, b) => (b[columnName]).compareTo(a[columnName]));
                else
                  _getsnapshot().sort((a, b) => (a[columnName]).compareTo(b[columnName]));
              

              DataColumnEx getDataColumnEx(String columnName) 
                return widget.columns.containsKey(columnName)
                    ? widget.columns[columnName]
                    : DataColumnEx(
                        show: true,
                        label: columnName,
                        tooltip: columnName,
                        numeric: false,
                        editable: false,
                        editableTextInputType: TextInputType.name);
              

              List<DataColumn> _getDataColumn() 
                List<DataColumn> listDataColumn = <DataColumn>[];

                for (var i = 0; i < _getsnapshot()[0].keys.length; i++) 
                  String columnName = _getsnapshot()[0].keys.elementAt(i).toString();

                  DataColumnEx column = getDataColumnEx(columnName);

                  if (false == column.show) continue;

                  listDataColumn.add(DataColumn(
                      onSort: (columnIndex, ascending) 
                        setState(() 
                          _sortColumnIndex = columnIndex;
                          _sortAscending = ascending;
                        );
                      ,
                      tooltip: column.tooltip,
                      label: Text(column.label, textAlign: TextAlign.center)));
                
                return listDataColumn;
              

              List<DataRow> _getDataRow() 
                List<DataRow> listDataRow = <DataRow>[];

                for (var rowIdx = 0; rowIdx < _getsnapshot().length; rowIdx++) 
                  List<DataCell> ldc = <DataCell>[];
                  for (var ceilIdx = 0;
                      ceilIdx < _getsnapshot()[0].keys.length;
                      ceilIdx++) 
                    String columnName =
                        _getsnapshot().first.keys.elementAt(ceilIdx).toString();

                    DataColumnEx column = getDataColumnEx(columnName);
                    if (false == column.show) continue;

                    String ceilText = _getsnapshot()[rowIdx][columnName].toString();

                    ldc.add(column.editable
                        ? DataCell(
                            TextFormField(
                              controller: TextEditingController(text: ceilText),
                              //initialValue: ceilText,
                              keyboardType: column.editableTextInputType,
                              onFieldSubmitted: (val) 
                                print('onSubmited $val');
                              ,
                            ),
                            showEditIcon: true,
                          )
                        : DataCell(Text(ceilText)));
                  

                  listDataRow.add(DataRow(
                    // color: MaterialStateColor.resolveWith((states) 
                    //   return rowIdx % 2 == 0 ? Colors.red : Colors.black; //make tha magic!
                    // ),
                    // color: MaterialStateProperty.resolveWith<Color>(
                    //     (Set<MaterialState> states) 
                    //   if (states.contains(MaterialState.selected))
                    //     return Theme.of(context).colorScheme.primary.withOpacity(0.58);
                    //   return null; // Use the default value.
                    // ),
                    // selected: true,
                    // onSelectChanged: (value) 
                    //   setState(() );
                    // ,
                    cells: ldc,
                  ));
                
                return listDataRow;
              

              @override
              Widget build(BuildContext context) 
                final size = MediaQuery.of(context).size;
                final width = size.width;
                final height = size.height;
                return Container(
                  width: width,
                  height: height,
                  child: SingleChildScrollView(
                    scrollDirection: Axis.vertical,
                    child: SingleChildScrollView(
                      scrollDirection: Axis.horizontal,
                      child: FutureBuilder<List<Map<String, dynamic>>>(
                        future: SqliteUtils(widget.dbName).executeReader(widget.sqlScript),
                        builder: (BuildContext c,
                            AsyncSnapshot<List<Map<String, dynamic>>> aspsh) 
                          debugPrint("build");

                          if (!aspsh.hasData)
                            return (Center(child: CircularProgressIndicator()));

                          _setsnapshot(aspsh);
                          //sleep(Duration(seconds: 5));
                          _sort();

                          return DataTable(
                            //headingRowColor:
                            //    MaterialStateColor.resolveWith((states) => Colors.blue),
                            sortAscending: _sortAscending,
                            sortColumnIndex: _sortColumnIndex,
                            showCheckboxColumn: false,
                            columns: _getDataColumn(),
                            rows: _getDataRow(),
                          );
                        ,
                      ),
                    ),
                  ),
                );
              
            

【讨论】:

以上是关于Flutter:使用来自 Firestore 的多集合流创建动态更新的 DataTable的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 中加入来自两个 Firestore 集合的数据?

使用来自firestore数据库的geoflutterfire查询结果,使用flutter应用程序返回null或啥都没有,但语法没有错误

在颤动中合并来自 Firestore 的流

来自Json的Flutter Firestore返回Null?

在 Flutter 中的垂直 ScrollView 内的水平 ListView 中显示来自 Firestore 的数据

有没有办法通过flutter查看来自云Firestore的用户的关注者或关注者?