GridView with Flutter and Firestore - flutter

I`m trying to make a simple GridView from a cloud firestore record. I've followed a lot of video tutorials but without success. Here's the code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class EventList extends StatefulWidget {
#override
EventListState createState() => new EventListState();
}
class EventListState extends State<EventList> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Firestore.instance.collection('events_flutter').snapshots(),
builder: (BuildContext context, DocumentSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: const Text('Loading events...'));
}
return GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return Text(snapshot['event_name']);
},
itemCount: snapshot.data.documents.length,
);
},
);}}
And this is the error message when hovering on "builder: (BuildContext context, DocumentSnapshot snapshot)".
Could anybody help me understand what is going on?
Thanks a lot.

You should replace the type of your snapshot from DocumentSnapshot to AsyncSnapshot.
...
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: const Text('Loading events...'));
}
...
And also, you might want to replace this line:
return Text(snapshot['event_name']);
to this:
return Text(snapshot.data.documents[index]['event_name']);

You collection().snapshot() returns QuerySnapshot, so you would have to change DocumentSnapshot to QuerySnapshot like this:
class EventListState extends State<EventList> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Firestore.instance.collection('events_flutter').snapshots(),
builder: (BuildContext context, QuerySnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: const Text('Loading events...'));
}
return GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return Text(snapshot['event_name']);
},
itemCount: snapshot.data.documents.length,
);
},
);}}

Related

displaying data from different firestore collections

I'm attempting display data from two diffrent collections within firestore , I treied to nest both streambuilds so i can particulary display the data as one stream , however I keep on getting the error bad state field doesnt exist with doc snapshot how can i fixing thus error , or is there another much more effective method i can use to display data from two diffrent collections in one class?
below is screenshot of the data(s) i want to display:
class OrderStream extends StatelessWidget {
static const String route = "/Order";
final CollectionReference meal =
FirebaseFirestore.instance.collection("menu");
final CollectionReference profile =
FirebaseFirestore.instance.collection("users");
OrderStream({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: profile.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
return StreamBuilder(
stream: meal.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (!streamSnapshot.hasData) {
return const SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
streamSnapshot.data!.docs[index];
return Column(
children: [
Text( documentSnapshot['price'],)
Text( documentSnapshot['name'],)
]
),
),
}
This is probably happening due to similar name for both snapshots.
The best way to check this is by renaming the snapshot for individual Streambuilder().
StreamBuilder(
stream: profile.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> profileStreamSnapshot) {
return StreamBuilder(
stream: meal.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> mealStreamSnapshot) {
if (!streamSnapshot.hasData) {
//modified (renamed snapshot variable) code here
}
You can merge those two streams into 1 using library like rxdart which has combineLatest2 method although you can also use something like StreamZip to get the same effect.
I have used rxdart combineLatest2 as follows:
import 'package:rxdart/rxdart.dart';//import ⇐
class MyHomePage extends StatelessWidget {
final CollectionReference profile =
FirebaseFirestore.instance.collection("users");
final CollectionReference meal =
FirebaseFirestore.instance.collection("menu");
MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Rx.combineLatest2(profile.snapshots(), meal.snapshots(),
(QuerySnapshot profileSnapshot, QuerySnapshot mealSnapshot) {
return [...profileSnapshot.docs, ...mealSnapshot.docs];
}),
builder: (context, AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
return const SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
snapshot.data![index];
final Map<String, dynamic> data =
documentSnapshot.data() as Map<String, dynamic>;
if (data.containsKey("price") && data.containsKey("name")) {
return Column(
children: [Text(data["price"]), Text(data["name"])],
);
} else {
return Container();
}
},
);
}
}),
);
}
}
You can also use Stream.merge() as follows:
final Stream<QuerySnapshot> mealsStream = meal.snapshots();
final Stream<QuerySnapshot> profilesStream = profile.snapshots();
//.. All that Scaffold stuff
stream: Stream.merge([mealsStream, profilesStream]),

The argument type 'User?' can't be assigned to the parameter type Future<Object?>?'

Hey there I am new to flutter and i am currently working on firebase with flutter I am getting the following error , The argument type 'User?' can't be assigned to the parameter type 'Future<Object?>?' ,The error occurs in the futurebuilder in future argument ,
class messages extends StatelessWidget {
const messages({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
builder: (ctx, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
final chatdoc = snapshot.data!.docs;
final userdata = FirebaseAuth.instance.currentUser;
return FutureBuilder(
future: userdata,
builder: (ctx, Snapshot) => ListView.builder(
reverse: true,
itemBuilder: (ctx, index) => messagebubble(
chatdoc[index]['Text'], chatdoc[index]['userId']),
itemCount: snapshot.data!.docs.length,
));
},
stream: FirebaseFirestore.instance
.collection('chats')
.orderBy('createdAt', descending: true)
.snapshots(),
);
}
}
The FirebaseAuth.instance.currentUser returns a User? not a Future. Thus, you can access it without the FutureBuilder, just return the messageBubble widget :
return messageBubble(
chatdoc[index]['Text'],
userData?.uid)

Flutter : Class 'Future<List<String>>' has no instance getter 'length'

I have a List of String saved on SharedPreference in Flutter app, I want to call it inside Provider and using it in a widget.
Provider :
get myTeam => getMyTeam();
Future<List<String>> getMyTeam() async {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
final SharedPreferences prefs = await _prefs;
return prefs.getStringList('team');
}
I used it in future builder :
Widget build(BuildContext context) {
return Consumer<GeneralProvider>(
builder: (context, generalProvider, child) {
var items = generalProvider.myTeam;
return FutureBuilder(
future: items,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index].club}'),
);
});
} else {
return Text('bad');
}
});
});
}
I get error : Class 'Future<List<String>>' has no instance getter 'length'
I tied with many solutions in questions here like :
Class 'Future<dynamic>' has no instance getter 'length'
but it wasn't solved
Change itemCount: items.length into itemCount: snapshot.length,
And Future builder to FutureBuilder<List<String>(...etc).
Will look like this in the end:
Widget build(BuildContext context) {
return Consumer<GeneralProvider>(
builder: (context, generalProvider, child) {
var items = generalProvider.myTeam;
return FutureBuilder<List<String>>(
future: items,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.length,
itemBuilder: (context, index) {
return ListTile(
// title: Text('${items[index].club}'),//This will likely throw an error also, because items is a List<String>, there is no method called "club" for Lists.
//Replace it with this to validate:
title: Text(snapshot[index]),//This
);
});
} else {
return Text('bad');
}
});
});
}

Error trying to build a ListView in a Flutter FutureBuilder

I am new to Flutter and building a small app to record my expenses and learn a bit.
I am using Hive to store data. Now I am building a page which targets to show all the previously saved entries. I do this by creating a List with all the data and then trying to use a FutureBuilder to show the data in a ListView.
This is the code so far:
class LogScreen extends StatefulWidget {
const LogScreen({Key? key}) : super(key: key);
#override
_LogScreenState createState() => _LogScreenState();
}
class _LogScreenState extends State<LogScreen> {
get futureEntries => getEntries();
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: futureEntries.length,
itemBuilder: (context, index) {
Entry currentEntry = Hive.box<Entry>('entriesBox').getAt(index);
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
}
I am getting the following error though:
The following _TypeError was thrown building LogScreen(dirty, state: _LogScreenState#75644):
type 'Future<List<dynamic>>' is not a subtype of type 'Future<Widget>?'
The relevant error-causing widget was:
LogScreen file:///home/javier/StudioProjects/finanzas/lib/main.dart:55:14
When the exception was thrown, this was the stack:
#0 _LogScreenState.build (package:finanzas/log_screen.dart:29:17)
Could someone please tell me what I am doing wrong and suggest a solution? I come from Python and am having a though time with all these types :-P
Thanks in advance.
The generic type of FutureBuilder<T>() should correspond to the data type your Future will return, not what the builder is building. In your case you have FutureBuilder<Widget> so it expects a Future<Widget>, but your getEntries returns a Future<List<dynamic>>. So this is what the error is hinting at. Your code should probably look like this:
return FutureBuilder<List<Entry>>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List<Entry>> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data[index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
Also note that i replaced the references in your ListView.builder from directly referencing your future to using the data inside the snapshot
Alright. After some research, here's the code that got to work:
Widget build(BuildContext context) {
return FutureBuilder<List>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data![index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
I don't know yet exactly what the exclamation marks after 'data' do, but they did the trick.

Item builder not getting triggered in futurbuilder Flutter

Scaffold(body: FutureBuilder(
future: fetchTracks(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.hasData)
{
ListView.builder(
scrollDirection: Axis.vertical,
itemExtent: 130.0,
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: trackes.length,
itemBuilder: (BuildContext context, int index) {
print("test");
return makeCard(snapshot.data[index]);
},
).build(context);
}
else
{
return Center(child: new CircularProgressIndicator());
}
} ));
When i call this Scaffold Future build will call my future function fetchTracks() and get the data in snapshot but it is not entering into itemBuilder function. So futurebuilder return NULL.
Please help me to solve .and Thank you in advance
You're missing a return before ListView.builder. If you don't return it, it won't build it.
FutureBuilder has different snapshot connectionstates which you must handle. Data on the stream is not available until ConnectionState equals done and hasData equals true.
_loadData(context)
{
showModalBottomSheet(
context: context,
builder: (BuildContext bc){
return FutureBuilder(
future: fetchTracks(),
builder: (BuildContext context, AsyncSnapshot<List<MyClass>> snapshot){
if (snapshot.connectionState!=ConnectionState.done)
{
return PleaseWaitWidget();
}
else if(snapshot.hasError)
{
DialogCaller.showErrorDialog(context,"future builder has an error").then((value){});
}
else if (snapshot.connectionState==ConnectionState.done)
{
if(snapshot.hasData){
List<Widget> list = snapshot.data.map((MyClass myClass){
return Card(
child:Wrap(children:<Widget>[
Row(children: [Text(myClass.field1)],),
]));}).toList();
return list;
}
}
});
});
}