0

I have an existing GroupedListView that I want to convert to (or replace with) an Expandable widget. My data source is from Firebase Firestore.

My code is extremely messy of all the "trying to figure it out" cases, so I will keep that as a last resort to keep the post as clean as possible. It kind of looks like this:

List<String> _headers = [];
List<Map<String, dynamic>> = [];
List finalList = [];
await FirebaseFirestore.instance.collection('collection').get().then((snapshot){
  for(final document in snapshot.docs){
    if(!_headers.contains(document.data()['headerDate'])){
      _headers.add(document.data()['headerDate']);
    }
  }
});

await FirebaseFirestore.instance.collection('collection').get().then((snapshot){
  for(headerItem in _headers){
    List<Map<String, dynamic>> tempData = [];
    for(final document in snapshot.docs){
      if(document.data()['headerDate'] == headerItem){
        tempData.add([{'name': document.data()['name'], 'idNo': document.data()['idNo'], document.data()['amount']});
      }
    }
    finalList.add([headerItem, tempData]);
  }
});

After this I am pretty much stuck and have no idea how to map the data under each headerItem to be displayed in an expandable group.

Is it safe to follow this structure or is there a possibility that I may get unordered results? Also, is there a cleaner way to write this code I currently have?

Thanks for your help with this!

Edits:

I am still not able to get this right. This is my implementation of how I understand it.

My code for the model:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:collection/collection.dart';

class AttendanceData {
  static final _collection =
      FirebaseFirestore.instance.collection('attendance2').withConverter(
            fromFirestore: _fromFirestore,
            toFirestore: _toFirestore,
          );
  final String headerDate;
  final String tagName;
  final String tagId;
  final String workerType;
  final String teamName;
  final bool clockedIn;
  final String timeIn;
  final String timeOut;
  final String employeeId;

  AttendanceData(
      {required this.tagName,
      required this.tagId,
      required this.workerType,
      required this.teamName,
      required this.clockedIn,
      required this.timeIn,
      required this.timeOut,
      required this.employeeId,
      required this.headerDate});

  static AttendanceData _fromFirestore(
      DocumentSnapshot<Map<String, dynamic>> snapshot,
      SnapshotOptions? options,
      ) =>
      AttendanceData.fromSnapshot(snapshot);

  static Map<String, dynamic> _toFirestore(
    AttendanceData assignment,
    SetOptions? options,
  ) =>
      assignment.toFirestore();

    factory AttendanceData.fromSnapshot(
    DocumentSnapshot<Map<String, dynamic>> snapshot,
  ) {
    final staffData = snapshot.data();
    return AttendanceData(
      headerDate: staffData?['headerDate'],
      tagName: staffData?['tag_name'],
      tagId: staffData?['tag_id'],
      workerType: staffData?['worker_type'],
      teamName: staffData?['team_name'],
      clockedIn: staffData?['clockedIn'],
      timeIn: staffData?['timeIn'],
      timeOut: staffData?['timeOut'],
      employeeId: staffData?['employeeId'],
    );
  }

  Map<String, dynamic> toFirestore() {
    return {
      'headerDate': headerDate,
      'tag_name': tagName,
      'tag_id': tagId,
      'worker_type': workerType,
      'team_name': teamName,
      'clockedIn': clockedIn,
      'timeIn': timeIn,
      'timeOut': timeOut,
      'employeeId': employeeId,
    };
  }

  static Stream<QuerySnapshot<AttendanceData>> snapshots() =>
      _collection.snapshots();

  Map<String, List<dynamic>> groupByHeaderDate(collectionData) {
    return groupBy(collectionData, (collection) => collection.headerDate);
  }
}

And then in the StreamBuilder

StreamBuilder(
  stream: AttendanceData.snapshots(),
  builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
    return staffData.keys.map((collection) =>
      ExpansionTile(
        title: collection.headerDate,
        subtitle: collection.tagName
      )).toList();
ben
  • 81
  • 8

1 Answers1

1

You can have a look at this: How to use Firestore withConverter in Flutter

It will give you an idea on how to construct a proper class with your data.

What I would do, is to first fetch the data with the converter, which then conveniently converts it into an object for you to use.

I would then use a groupBy method to group the data by a specific attribute (You can include this method in your object class).

I would then map this data into the expandables that you want.

import 'package:collection/collection.dart';

class Collection {
  static final _collection =
      FirebaseFirestore.instance.collection('collection').withConverter(
            fromFirestore: _fromFirestore,
            toFirestore: _toFirestore,
          );
  final Date headerDate;
  final String name;
  etc...
    
  Collection({
    required this.headerDate,
    required this.name,
    etc...
  })

  static Collection _fromFirestore(
    DocumentSnapshot<Map<String, dynamic>> snapshot,
    SnapshotOptions? options,
  ) =>
      Collection.fromSnapshot(snapshot);

  static Map<String, dynamic> _toFirestore(
    Collection assignment,
    SetOptions? options,
  ) =>
      assignment.toFirestore();

  factory Collection.fromSnapshot(
    DocumentSnapshot<Map<String, dynamic>> snapshot,
  ) {
    final data = snapshot.data();
    return Collection(
      headerDate: DateTime.parse(data['headerDate']),
      name: data['name'],
      etc...
    );
  }

  Map<String, dynamic> toFirestore() {
    return {
      'headerDate': headerDate.toIso8601String,
      'name': name,
      etc...,
    };
  }

  Map<Date,List<dynamic>> groupByHeaderDate(Collection collectionData) {
    return groupBy(collectionData, (Collection collection) => collection.headerDate);
  }
}

And then you can simply use the data from groupByHeaderDate like so:

data.keys.map((collection) => Expandable(..properties..)).toList()

  • Thanks Lodewyk. This looks as if it will work, just a few questions. 1, I get a warning that `_collection` is not used in the class. 2, when adding `data.keys...` it throws an error "Undefined name 'data'". I placed your example in a class and the data section in the return body of the `StreamBuilder`. Am I doing this right or missing the mark here? 3, I also get an error "'groupBy' isn't defined for type 'Collection'". – ben Mar 19 '23 at 12:02
  • You can add this to the model: `static Stream> snapshots() => _collection.snapshots();` And then you can listen to the snapshots on the page with the expandable, using this info here: https://stackoverflow.com/questions/50471309/how-to-listen-for-document-changes-in-cloud-firestore-using-flutter . As for the groupBy, make sure you have collection in your pubspec.yaml as a dependency, and make sure to import the collections, like at the top of my code example. – Lodewyk Roux Mar 19 '23 at 13:28