How would you implement a pull-down to refresh in Flutter app that gets data from a collection in Firestore preferable using a StreamBuilder or a FutureBuilder and displays it in a ListView ?
I ended up using information from here. How to refresh or reload a flutter firestore streambuilder manually?
I added the Refresh indicator and made the my stream get its data from a function see code below.
var stream;
#override
void initState() {
setState(() {
stream = mouvesStream();
});
super.initState();
}
Stream<QuerySnapshot> stream() {
return Firestore.instance.collection(_locationState).snapshots();
}
#override
Widget build(BuildContext context) {
body: StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text(snapshot.error);
}
if (snapshot.connectionState == ConnectionState.active) {
List aList = new List();
aList.clear();
for (DocumentSnapshot _doc in snapshot.data.documents) {
Model _add = new Model.from(_doc);
aList.add(_add);
}
return TabBarView(
children: <Widget>[
RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView.builder(
itemCount: aList.length,
itemBuilder: (context, index) {
return Card(aList[index]);
},
),
),
Icon(Icons.directions_transit),
],
);
} else {
return Container(
child: Center(child: CircularProgressIndicator()));
}
})));
}
Related
what I am trying to achieve is load data from a specific collection(teacher) of my database. So I am using a function called isTeacher(). which checks if the current user's uid belongs in that collection. if not then it is a student. it stores the value in a string called value. so when I am using stream builder to load data available in their specific collection or documents, my stream builder shows circular progress and after that, it doesn't load the data. Any help is appreciated.
Thank you
`class MyClasses extends StatefulWidget {
#override
_MyClasses createState() => _MyClasses();
}
String value;
String classPassword;
List<dynamic> catchUserDetails = [];
class _MyClasses extends State<MyClasses> {
Future isTeacher() {
return FirebaseFirestore.instance
.collection('teacher')
.doc(FirebaseAuth.instance.currentUser.uid)
.get()
.then((DocumentSnapshot doc) {
value = doc.exists.toString();
print(doc.data());
print(value);
print('isteacher called in method');
});
}
#override
Widget build(BuildContext context) {
isTeacher();
return Scaffold(
body: SafeArea(
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection(value)
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('class')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
} else {
final messages = snapshot.data.documents.reversed;
List<GenerateClass> messageBubbles = [];
for (var message in messages) {
final messageText = message.data()['className'];
final messageBubble = GenerateClass(
classID: messageText,
//nnouncementID: i,
);
messageBubbles.add(messageBubble);
}
return ListView(
//itemExtent: 100,
children: messageBubbles,
);
}
},
),
),
);`
Solved it by using a FutureBuilder
FutureBuilder(
future: isTeacher(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return StreamBuilder();
Below you can see a code where I want to display data that I have collected from a registration form in Flutter.
the registration form push the data to a collection called " user " then some other documents data are pushed as:
name - email etc...
As you can see by XXXX I want that the data I retrieve from cloud firestore be shown into the widget:
Below the code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class WelcomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.pink,
body: MainWelcome(),
);
}
}
class MainWelcome extends StatefulWidget {
#override
_MainWelcomeState createState() => _MainWelcomeState();
}
class _MainWelcomeState extends State<MainWelcome> {
final databaseReference = Firestore.instance;
Future<QuerySnapshot> getData() async {
return await Firestore.instance.collection("user").getDocuments();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
snapshot.data.documents.forEach((element) {
Center(
child: Text(
'Benvenuta ${element.data["name"]}',
style: TextStyle(fontSize: 20, color: Colors.white),
),
);
});
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
}
return Center(child: CircularProgressIndicator());
},
);
}
}
You need to use a FutureBuilder widget to display the data in the widget tree:
Create a method that returns the data:
Future<QuerySnapshot> getData() async {
return await Firestore.instance
.collection("user")
.where("email", isEqualTo: "email_here")
.getDocuments();
}
Then inside the build() method do the following:
FutureBuilder(
future: getData(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
contentPadding: EdgeInsets.all(8.0),
title:
Text(snapshot.data.documents[index].data["name"]),
);
});
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
}
return CircularProgressIndicator();
},
),
I have an app which I want to display documents inside collection.. the collection reference is the uid of the user.
Is there a way to get current user uid and put this uid inside StreamBuilder in stream.
I have tried like so but it did not work and returned null:
class _MyAdsState extends State<MyAds> {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future getCurrentUser() async {
final FirebaseUser user = await _auth.currentUser();
final uid = user.uid;
print(uid);
return uid.toString();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("${getCurrentUser()}").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapShot){
if(querySnapShot.hasError){
return Text('Some Error');
}
if(querySnapShot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
}else{
final list = querySnapShot.data.documents;
return ListView.builder(
itemBuilder: (context, index){
return ListTile(
title: Text(list[index]["subject"]),
subtitle: Text(list[index]["category"]),
);
},
itemCount: list.length,
);
}
},
)
Getting the UID is an asynchronous operation, so requires a FutureBuilder.
If you want to use the UID to then build a stream, you'll need to have a FutureBuilder for the UID, and then inside of that a StreamBuilder for the stream from the database.
body: FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection(snapshot.data.uid).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapShot){
...
},
)
}
else {
return Text('Loading user data...');
}
THANK YOU GUYS!
I was looking for this for too long now. I had the "problem" that I was recording the senderUID for a sent message only, but of course wanted the Name being displayed in the "sentFrom" field. So I had to query Firestore for the UID and pull out the email. My solution:
FutureBuilder<QuerySnapshot>(
future: _firestore.collection("users").get(),
builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
Map<String, String> users = {};
final userData = futureSnapshot.data.docs;
for (var user in userData) {
users[user.id] = user.data()["email"];
}
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection("messages").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
// ignore: missing_return
final messages = snapshot.data.docs;
List<Widget> messageWidgets = [];
for (var message in messages) {
final messageText = message.data()["text"];
final messageEmail = users[message.data()["senderUID"]];
messageWidgets
.add(Text("$messageText from $messageEmail"));
}
return Column(children: messageWidgets);
},
);
},
),
I just created a map from the data and used it inside the stream builder. Is there maybe a better solution?
Hi I am trying to display the data inside the Firebase documents into my Flutter dynamically where they get rendered using a loop, so I made a List<Widget> Cards and added to it the function makeItem() that contains the cards, and put them inside a loop, so the problem is that when I run the code it outputs print(snapshot.connectionState); as ConnectionState.waiting all the time and it should be async snapshot yet it refuses to load the data as required, I should mention that the data is display as wanted when I hit "Hot reload in Android Studio" .
so I don't know how resolve this issue. Thanks in Advance
I had the same problem when using STREAM BUILDER with PROVIDER&CHANGE NOTIFIER.
When returning back to the view, one should re-assign the stream itself.
Make a get function for your stream and in that function before returning your stream re-assign the stream. That solved the problem of loading issue for me.
Can you try the following?
class MyList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection(widget.city).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Center(child: CircularProgressIndicator(backgroundColor: Colors.amber,strokeWidth: 1),),
default:
return ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return makeItem(
pointName: document['name'],
huge: document['lastname'],
moderate: document['mobileNumber'],
none: document['location'],
fights: document['job'],
);
}).toList(),
);
}
},
);
}
}
I think I got something for you try this out. It works on my emulator.
List<Widget> cards = [];
Stream<QuerySnapshot> firebaseStream;
#override
void initState() {
super.initState();
firebaseStream = Firestore.instance.collection('Hearings').snapshots();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder<QuerySnapshot>(
stream: firebaseStream,
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> asyncSnapshot) {
List<DocumentSnapshot> snapData;
if (asyncSnapshot.connectionState == ConnectionState.waiting) {
return Container(
child: Center(
child: CircularProgressIndicator(
backgroundColor: Colors.amber,
strokeWidth: 1,
),
),
);
} else if (asyncSnapshot.connectionState ==
ConnectionState.active) {
snapData = asyncSnapshot.data.documents;
if (asyncSnapshot.hasData) {
for (int i = 0; i < snapData.length; i++) {
Widget card = Text(snapData[i].data['locationName']);
cards.add(card);
}
}
}
return ListView.builder(
itemCount: cards.length,
itemBuilder: (context, index) => cards[index],
);
},
),
),
);
I got bad news too though now that the data is updating it exposed some flaws in your logic its duplicating old entries in your array. You'll see. That should be easy to fix though.
Using stream.cast() on StreamBuilder solved my problem.
I am having extreme difficulty in handling this. I call an async function that will get some info from SQLite but I can't seem to get it. It just renders a empty screen in which should be a listview.
List allItems = new List();
Future<void> pegaDados() async {
var itens = await geraCardapio();
for (var i = 0; i < itens.length; i++) {
print((itens[i].toMap()));
allItems.add(itens[i].toMap());
}
}
print(pegaDados());
return ListView.builder(
itemCount: allItems.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset("assets/"+ allItems[index]['imagem'], fit: BoxFit.contain,),
title: Text(allItems[index]['pedido']),
trailing: Text(allItems[index]['valor']),
);
},
);
Thank you very much.
I managed to get the solution, thanks to both people who answered the question (using both solutions I managed to get this little frankenstein)
Future<dynamic> pegaDados() async {
var allItems = await geraCardapio();
return allItems.map((allItems) => allItems.toMap());
}
return FutureBuilder(
future: pegaDados(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
print(snapshot.data);
var objeto = [];
for (var i in snapshot.data) {
objeto.add(i);
}
print(objeto);
return Container(
child: ListView.builder(
itemCount: objeto.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset("assets/"+ objeto[index]['imagem'], fit: BoxFit.contain),
title: Text(objeto[index]['pedido']),
trailing: Text(objeto[index]['valor'].toString()),
);
},
),
);
} else if (snapshot.hasError) {
throw snapshot.error;
} else {
return Center(child: CircularProgressIndicator());
}
});
thanks to [Mohammad Assem Nasser][1] and [Eliya Cohen][2] for the help!
[1]: https://stackoverflow.com/users/11542171/mohammad-assem-nasser
[2]: https://stackoverflow.com/users/1860540/eliya-cohen
You should first understand what is Future operations (Future function in your case). Future operations are the operations which take time to perform and return the result later. To handle this problem, we use Asynchronous functions.
Asynchronous Function let your program continue other operations while the current operation is being performed. Dart uses Future objects (Futures) to represent the results of asynchronous operations. To handle these operations, we can use async/await, but it is not possible to integrate async and await on widgets. So it is quite tricky to handle futures in widgets. To solve this problem flutter provided a widget called FutureBuilder.
In FutureBuilder, it calls the Future function to wait for the result, and as soon as it produces the result it calls the builder function where we build the widget.
Here is how it should be:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List allItems = new List();
Future<List> pegaDados() async{
var items = await geraCardapio(); // TODO: Add this function to this class
for (var i = 0; i < items.length; i++) {
print((items[i].toMap()));
allItems.add(items[i].toMap());
}
return items;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: Text('Demo')),
body: FutureBuilder(
future: pegaDados(),
builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.done){
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset("assets/"+ snapshot.data[index]['imagem'], fit: BoxFit.contain,),
title: Text(snapshot.data[index]['pedido']),
trailing: Text(snapshot.data[index]['valor']),
);
},
),
);
}
else if(snapshot.hasError){
throw snapshot.error;
}
else{
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}
Here is the link to a short video that will explain FutureBuilder in a concise way.
I'm not sure how your widget tree looks like, but I'm assuming ListView is being built simultaneously with pegaDados. What you're looking for is a FutureBuilder:
Future<dynamic> pegaDados() async{
var items = await geraCardapio();
return items.map((item) => item.toMap());
}
...
FutureBuilder<dynamic>(
future: pegaDados(),
builder: (BuilderContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Uninitialized');
case ConnectionState.active:
case ConnectionState.waiting:
return Text('Awaiting result...');
case ConnectionState.done:
if (snapshot.hasError)
throw snapshot.error;
//
// Place here your ListView.
//
}
return null; // unreachable
}