Christopher Armstrong
Christopher Armstrong

Reputation: 3557

Convert Firestore DocumentSnapshot to Map in Flutter

I need to update a document with nested arrays in Firestore with Flutter.

So I need to get the full document into a Map, reorder the maps in the "sections" array, and then store the data back into the document.

I'm however unfamiliar how I can get the data of the snapshot (DocumentSnapshot) into a Map.

Below an example that doesn't work of what I try to achieve:

final Map<String, dynamic> doc = snapshot.data as Map<String, dynamic>;

"snapshot.data" contains the values of the document. The structure of the document looks like this:

{
  name: "Course 1"
  sections: [
    {name: "Section 1"},
    {name: "Section 2"}
  ]
}

Once the Maps in the sections array have been re-ordered, I need to save the data back into the document.

Here the full function. Relevant code is in "onDragFinish".

 // Build editable list with draggable list tiles and swipe to delete
  List<Widget> buildListViewEdit() {
    final course = db.collection("school").document("3kRHuyk20UggHwm4wrUI")
      .collection("course").document("74UsE9x7Bsgnjz8zKozv").snapshots();

    return [
      StreamBuilder(
        stream: course,
        builder: (context, snapshot) {
          if (!snapshot.hasData) return const Text("Loading...");

          return Expanded(
            child: DragAndDropList(
              snapshot.data["sections"].length,
              itemBuilder: (context, index) {
                return Card(
                  child: ListTile(
                    title: Text(snapshot.data["sections"][index]["name"]),
                    onTap: () {
                      print("hello");
                    }                    
                  )
                );
              },
              onDragFinish: (before, after) {
                print('on drag finish $before $after');

                //final docString = snapshot.data.toString();

                final Map <String, dynamic> doc = snapshot.data;

                //final tempSections = List.castFrom(snapshot.data["sections"]).toList();

                //Map data = tempSections[before];

                //tempSections.removeAt(before);
                //tempSections.insert(after,data);

                //snapshot.data["sections"] = tempSections;

                //db.collection("school").document("3kRHuyk20UggHwm4wrUI")
                  //.collection("course").document("74UsE9x7Bsgnjz8zKozv").updateData(snapshot.data);

                //var line = snapshot.data["sections"][before];

                //snapshot.data["sections"].removeAt(before);
                //snapshot.data["sections"].insert(after,line);

                /*
                List<Map> sections = docCopy["sections"];

                Map data = docCopy["sections"][before];
                sections.removeAt(before);
                sections.insert(after, data);
                print(sections);   
                */         
              },
              canDrag: (index) {
                print('can drag $index');
                return index != 3;
              },
              canBeDraggedTo: (one, two) => true,
              dragElevation: 8.0,
            )
          );
        }
      )
    ];   
  }

Error when trying to copy snapshot.data into another variable:

flutter: ══╡ EXCEPTION CAUGHT BY GESTURE LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown while routing a pointer event:
flutter: type 'DocumentSnapshot' is not a subtype of type 'Map<String, dynamic>'
flutter:
flutter: Either the assertion indicates an error in the framework itself, or we should provide substantially
flutter: more information in this error message to help you determine and fix the underlying cause.
flutter: In either case, please report this assertion by filing a bug on GitHub:
flutter:   https://github.com/flutter/flutter/issues/new?template=BUG.md
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0      _SectionScreenState.buildListViewEdit.<anonymous closure>.<anonymous closure> (package:teach_mob/screens/section_screen.dart:150:45)

Working example

Thanks all for your assistance. Here a full example that worked for me:

  // Build editable list with draggable list tiles and swipe to delete
  List<Widget> buildListViewEdit() {
    final course = db.collection("school").document("3kRHuyk20UggHwm4wrUI")
      .collection("course").document("74UsE9x7Bsgnjz8zKozv").snapshots();

    return [
      StreamBuilder(
        stream: course,
        builder: (context, snapshot) {
          if (!snapshot.hasData) return const Text("Loading...");

          return Expanded(
            child: DragAndDropList(
              snapshot.data["sections"].length,
              itemBuilder: (context, index) {
                return Card(
                  child: ListTile(
                    title: Text(snapshot.data["sections"][index]["name"]),
                    onTap: () {
                      print("hello");
                    }                    
                  )
                );
              },
              onDragFinish: (before, after) {
                print('on drag finish $before $after');

                // Convert AsyncSnapshot to DocumentSnapshot and then
                // create a map that can be changed and updated.
                final Map <String, dynamic> doc = snapshot.data.data;

                // Convert fixed length list to dynamic list, because items in
                // fixed length lists can't be added / removed.
                final tempSections = List.castFrom(doc["sections"]).toList();

                // Get the data of the list item to be dragged
                // Remove the data from the current position
                // Add the data to the new position of the list
                Map data = tempSections[before];

                tempSections.removeAt(before);
                tempSections.insert(after,data);

                // Overwrite sections with new list array
                doc["sections"] = tempSections;

                // Store the data back into the firestore document
                db.collection("school")
                  .document("3kRHuyk20UggHwm4wrUI")
                  .collection("course")
                  .document("74UsE9x7Bsgnjz8zKozv")
                  .updateData(doc);
              },
              canDrag: (index) {
                print('can drag $index');
                return index != 3;
              },
              canBeDraggedTo: (one, two) => true,
              dragElevation: 8.0,
            )
          );
        }
      )
    ];   
  }

Upvotes: 21

Views: 63097

Answers (8)

embarker
embarker

Reputation: 194

Building on above answers you can try something like

 Position p = snapshot.data as Position;

Upvotes: 0

Wege
Wege

Reputation: 337

I think you are all wrong here.

The only way to get Map field from Firestore database is using this code docs.get("MapVariableName") as Map<String, dynamic>, like in this example code below:

 Widget userFstore(BuildContext context, DocumentSnapshot docs, int index) {
    //Assign it into new Map using variable late Map
    late Map<String, dynamic> userInfo = docs["user"] as Map<String, dynamic>;
    //Then access its key like this method below
    late String name = userInfo["name"] as String;
    late String imgURL = userInfo["imgURL"] as String;
    late String imgName = userInfo["imgName"] as String;
    late String desc = userInfo["desc"] as String;
    late String email = userInfo["email"] as String;
    late bool active = userInfo["active"] as bool;
    
    return const SizedBox();
 }

Upvotes: -1

Saed Nabil
Saed Nabil

Reputation: 6861

As per our discussion snapshot is not a DocumentSnapshot it is AsyncSnapshot

to get the DocumentSnaphot use snapshot.data

to get the actual map you can use snapshot.data.data()

Which will return the Map<String, dynamic> you are looking for.

Upvotes: 32

Brandon Pillay
Brandon Pillay

Reputation: 1166

Update May 2021

See Migration to cloud_firestore 2.0.0 here.

//Replace this:
- StreamBuilder<DocumentSnapshot>(
//With this:
+ StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(

The type has to be defined when creating variables:

Future<void> example(
    -  DocumentReference documentReference,
    +  DocumentReference<Map<String, dynamic>> documentReference,
    -  CollectionReference collectionReference,
    +  CollectionReference<Map<String, dynamic>> collectionReference,
    -  Query query,
    +  Query<Map<String, dynamic>> query,
) {

Upvotes: 18

Tiago Santos
Tiago Santos

Reputation: 766

Updated September-2020

To get data from a snapshot document you now have to call snapshot.data() (cloud_firestore 0.14.0 or higher)

Upvotes: 5

saigopi.me
saigopi.me

Reputation: 14908

To get map from documentsnapshot use snapshot.data.data

Upvotes: 2

Jan
Jan

Reputation: 1492

Looks like maybe because you got a stream builder, so the Snapshot is a AsyncSnapshot<dynamic>, when you grab its .data, you get a dynamic, which returns a DocumentSnapshot, which then you need to call .data on this object to get the proper Map<String, dynamic> data.

builder: (context, snapshot) {
final DocumentSnapshot  ds = snapshot.data;
final Map<String, dynamic> map = ds.data;
}

Snapshot Data Doc Data

You can also append to arrays using in-built functions, but looks like you wanna do some crazy sorting so all good.

Upvotes: 7

Aaron Saunders
Aaron Saunders

Reputation: 33335

Needed to simplify for purpose of example enter image description here

class ItemsList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // get the course document using a stream
    Stream<DocumentSnapshot> courseDocStream = Firestore.instance
        .collection('Test')
        .document('4b1Pzw9MEGVxtnAO8g4w')
        .snapshots();

    return StreamBuilder<DocumentSnapshot>(
        stream: courseDocStream,
        builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
          if (snapshot.connectionState == ConnectionState.active) {

            // get course document
            var courseDocument = snapshot.data.data;

            // get sections from the document
            var sections = courseDocument['sections'];

            // build list using names from sections
            return ListView.builder(
              itemCount: sections != null ? sections.length : 0,
              itemBuilder: (_, int index) {
                print(sections[index]['name']);
                return ListTile(title: Text(sections[index]['name']));
              },
            );
          } else {
            return Container();
          }
        });
  }
}

The Results enter image description here

Upvotes: 9

Related Questions