Reputation: 3557
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
Reputation: 194
Building on above answers you can try something like
Position p = snapshot.data as Position;
Upvotes: 0
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
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
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
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
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;
}
You can also append to arrays using in-built functions, but looks like you wanna do some crazy sorting so all good.
Upvotes: 7
Reputation: 33335
Needed to simplify for purpose of example
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();
}
});
}
}
Upvotes: 9