Bad state: field does not exist within the DocumentSnapshotPlatform -flutter - flutter

So, all I'm trying to do is create a StreamBuilder that listens to the "raids" collection on Firebase, and return a widget for each document using a ListView.builder (though I'm not entirely sure this is the right way to go about this, I'm pretty new).
From everything I've seen, my code should be working properly but obviously I've misunderstood something along the way.
I've already confirmed that the field I'm trying to pass into my Text widget is accurate and that there is data within the snapshots, what do I do next?
class HostedRaids extends StatefulWidget {
const HostedRaids({Key? key}) : super(key: key);
#override
State<HostedRaids> createState() => _HostedRaidsState();
}
class _HostedRaidsState extends State<HostedRaids> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: (FirebaseFirestore.instance.collection('raids').snapshots()),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
var raidSnapshot = snapshot.data!.docs[index];
return Row(
children: [
Text(
raidSnapshot['creatorID'],
style: const TextStyle(color: Colors.white),
),
],
);
},
);
} else {
throw ('error');
}
});
}
}

Related

Get an Integer with Flutter from the Cloud Firestore

I am new at Flutter and I try to build an App. I want to Display an double on a Textwidget from Cloud Firestore.
My CloudFirestore look like this:
How can I read the double of 200 in a TextWidget and Displays all the time the new value?
I have create a function to add Integer on the specific logged In User Mail what looks like this:
geldNew = _firestore.collection('guthaben').doc(loggedInUser?.email).set({
'geld': geld,
});
You can use a StreamBuilder and the snapshots to stream the value, so when there's a change, the UI gets updated automatically like so:
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection('guthaben')
.doc("loggedInUser?.email")
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return const CircularProgressIndicator();
}
return Text((snapshot.data!.data() as Map)["geld"].toString());
},
),
),
);
}
}
If you want to get the value just once, you can use a FutureBuilder like so:
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance
.collection('guthaben')
.doc("loggedInUser?.email")
.get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return const CircularProgressIndicator();
}
return Text((snapshot.data!.data() as Map)["geld"].toString());
},
),
),
);
}
}

Saving the data to a dynamic list on another screen based on previous selection in flutter

Am a completely new flutter dev. I am trying to save a document from a queried firestore list on another saved documents page like an add to cart functionality. Am passing doc id as arguments to another page from firestore so that I get data based on the previous selection. Now how can I send the firestore reference and save it to the other screen without navigating to it so that users are able to save their favorite docs on another page and access them? Here is my Assignment page that lists the docs based on the previous selection.
class Assignments extends StatelessWidget {
final String programId;
final String yearId;
final String semesterId;
final String courseId;
const Assignments(
{Key key, this.programId, this.yearId, this.semesterId,
this.courseId})
: super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar2(title: 'Assigment'.toUpperCase(), ),
body: Column(
children: [
Expanded(
child: ContentArea(
addPadding: false,
child: StreamBuilder(
stream:
getAssignment(programId, yearId, semesterId, courseId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
color: kOnSurfaceTextColorYellow),
);
}
return ListView.separated(
padding: UIParameters.screenPadding,
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
final data = snapshot.data.docs[index];
return DisplayCard(
title: data['nameOfAssignment'],
icon: Icons.add,
// Here is the action i want that should save the documment to
// the SavedPage empty list without navigating to it
onTapIconSave: (){}
onTap: () => Get.to(Pdf(
nameOfAssignment: data['nameOfAssignment'],
pdfUrl: data['pdfUrl'],
)),
);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(
height: 10,
);
},
);
})),
),
],
),
);
}
}
Here is the SavedPage which may be similar to the cart page. Am not sure what to do in order to save the Document from the Assignment Page in a Dynamic growable list
class Saved extends StatefulWidget {
const Saved({ Key key }) : super(key: key);
#override
State<Saved> createState() => _SavedState();
}
class _SavedState extends State<Saved> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar2(title: 'Saved'),
body: Column(
children: [],
),
);
}
}
You can add a state management package like Provider or Bloc, also you could save your data in your local database and access them from there. I recommend Provider, easy to use, and its what you need.

Bad state: field does not exist within the DocumentSnapshotPlatform Flutter Firestore

I'm having troubles with the error in the question title, followed the official flutter tutorial: https://www.youtube.com/watch?v=DqJ_KjFzL9I
Solved all errors that the IDE marked, and now my code has no errors but only get this message when I run the app.
my firestore db:
My code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class Cloudtest extends StatefulWidget {
const Cloudtest({Key? key}) : super(key: key);
#override
_CloudtestState createState() => _CloudtestState();
}
class _CloudtestState extends State<Cloudtest> {
Widget _buildListItem(BuildContext context,DocumentSnapshot document){
return ListTile(
title: Text(document['name'])
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance.collection('todo').snapshots(),
builder: (context,AsyncSnapshot snapshot){
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context,index) =>
_buildListItem(context,snapshot.data!.docs[index]),
);
}
),
);
}
}
What I expected to get was a list with the elements in the database, I call this page with a button in another flutter page, the rest of the app works fine.
Any help aprecciated!
You still need to extract the data out of the DocumentSnapshot, as in:
Widget _buildListItem(BuildContext context,DocumentSnapshot document){
var data = document.data() as Map<String, dynamic>;
return ListTile(
title: Text(data['name'])
);
}
Also I don't see the name property in the Firebase document structure, you do have title and description, though.

How to pass a Firestore document from a stream to next page in Flutter?

I have a listview where each item is one document from a firestore collection. I would like to tap the item and pass the document information to a details page.
This is how I am retrieving document information within the first stream:
child: Text(streamSnapshot.data.docs[index]['event_title'],
This is how I'm attempting to send the data to the next page:
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, EventPage.id, arguments: streamSnapshot.data.docs[index]);
},
I'm lost as to how to receive the passed data:
class _EventPageState extends State<EventPage> {
#override
final db = FirebaseFirestore.instance;
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('event_title'),
),
child: Column(
I know I need a StreamBuilder on the next page, but do you have any insight on how to make that stream show only the passed in document?
Why don't you use Provider instead? It'll help you to avoid boiler plate and as you're using streams it'll let you handle information in a better way. Check about it here
I have worked out an answer to this question. I'm sure there are several ways to do this, but here's mine:
The key is to pass the firestore document ID to the next page. In this example code, I pass streamSnapshot.data.docs[index].id.toString() as a parameter to a custom widget. I've located my named route within that widget.
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('events')
.where('start_date', isGreaterThanOrEqualTo: DateTime.now())
.snapshots(),
builder: (context, AsyncSnapshot streamSnapshot) {
if (!streamSnapshot.hasData) {
return SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else
return SizedBox(
height: 250,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: streamSnapshot.data.docs.length,
itemBuilder: (ctx, index) =>
EventListHorizontalTile(
//passes the document ID as a string down to the horizontally scrollable tile,
//where we push a named route with the docID string as an argument
firestoreDocID: streamSnapshot.data.docs[index].id.toString(),
image: streamSnapshot.data.docs[index]['main_image'],
name: streamSnapshot.data.docs[index]['name'],
),
),
);
}),
I then created a class to pass as an argument through a named route.
class Events {
final String firestoreDocID;
Events({
required this.firestoreDocID,
});
}
Now, within my EventListHorizontalTile widget:
class EventListHorizontalTile extends StatelessWidget {
const EventListHorizontalTile({
Key? key,
required this.name,
this.firestoreDocID = '',
}) : super(key: key);
final String name;
final String firestoreDocID;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
//Here I am pushing a named route with an argument, using that Events class I made earlier.
Navigator.pushNamed(context, EventPage.id, arguments: Events(firestoreDocID: firestoreDocID));
},
//child: The rest of the list tile widget
),
Now we have to write a bit of code in the EventPage to receive the argument.
class EventPage extends StatefulWidget {
const EventPage({
Key? key,
}) : super(key: key);
static String id = 'EventPage';
#override
_EventPageState createState() => _EventPageState();
}
class _EventPageState extends State<EventPage> {
#override
Widget build(BuildContext context) {
//This is how we receive the argument.
final args = ModalRoute.of(context)!.settings.arguments as Events;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//Some text to see if the string made it.
Text(args.firestoreDocID),
]),
),
);
}
}
And that's it! Once you have that document ID in your new page, you can call a Streambuilder like this:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('events')
.doc(args.firestoreDocID)
.snapshots(),

How to append new ListView items at the top

I am fetching ListView.builder() items from Cloud Firestore inside a StreamBuilder. I want the new item to be added at the top of the ListView. How can I do that? I tried reverse : true , though it reverse the ListView, but when there is only 2/3 items, the ListView looks ugly, as the ListView starts from bottom and the upper portion of the screen remains empty.
Added shrinkWrap: true and put the ListView inside an Align widget with alignment: Alignment.topCenter and got the result I wanted!
Align(
alignment: Alignment.topCenter,
child: ListView.builder(
reverse: true,
shrinkWrap: true,
...
...
)
)
if you want to add new item at the top of the the listview and if you are
using firestore used Timestamp
firebase. firestore. Timestamp
A Timestamp represents a point in time independent of any time zone
use this method
Timestamp.now()
fetch data from firestore order by time using StreamBuilder
Widget item() =>
StreamBuilder<QuerySnapshot>(
//fetch data from friends collection order by time
stream: Firestore.instance.collection("friends").orderBy(
"time", descending: true).snapshots(),
builder: (context, snapshot) {
//if data not exist show loading indicator
if (!snapshot.hasData)
return CircularProgressIndicator();
//if data exist
return ListView.builder(itemBuilder: (context, index) {
return Text(snapshot.data.documents[index].data['name']);
});
},
);
add data in firestore
Firestore.collection("friends")
.document(friends.otherUID)
.setData({"name" : "xyz","time": Timestamp.now()});
if you want to update item and you want to add updated item at the top of the listview
update data
Future<void> updatefriends({String name,Timestamp time}) async {
Map<String, Object> updateFriend = Map();
if (name.isNotEmpty) updateFriend['name'] = name;
if (time !=null) updateFriend['time']=time;
Firestore.instance.collection("friends")
.document(uid)
.updateData(updateFriend);
}
Just create a variable with the reverse index of the snapshots lenght, here is an example:
StreamBuilder(
stream: Firestore.instance.collection('news').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) return Text('Loading...');
int reverseIndex = snapshot.data.documents.length;
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
reverseIndex -=1;
return _buildItems(context, snapshot.data.documents[reverseIndex]);
});
}),
Keep in mind what comes first in your list, ie at the top, are the items that are in the first positions of your array. If the case, you can invert the array.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<int> Items = [5, 4, 3, 2, 1];
void _incrementItems() {
setState(() {
Items = List.from([9, 8, 7, 6])..addAll(Items);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: ListView.builder(
itemCount: Items.length,
itemBuilder: (context, index) {
return Text(
"Item "+Items[index].toString());
},
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementItems,
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
StoreConnector<_ViewModel, List<Message>>(
converter: (store) {
// check mark ,reverse data list
if (isReverse) return store.state.dialogList;
return store.state.dialogList.reversed.toList();
},
builder: (context, dialogs) {
// Add a callback when UI render after. then change it direction;
WidgetsBinding.instance.addPostFrameCallback((t) {
// check it's items could be scroll
bool newMark = _listViewController.position.maxScrollExtent > 0;
if (isReverse != newMark) { // need
isReverse = newMark; // rebuild listview
setState(() {});
}
});
return ListView.builder(
reverse: isReverse, // if it has less data, it will false now;
controller: _listViewController,
itemBuilder: (context, index) => _bubbleItem(context, dialogs[index], index),
itemCount: dialogs.length,
);
},
)