How to display list of data in application? - flutter

I have encountered an error where the data is printed out in terminal but do not display in the application.
Future<DocumentSnapshot> getTeacher() async {
var firebaseUser = await FirebaseAuth.instance.currentUser;
var docRef = FirebaseFirestore.instance.collection("User");
var query = docRef.where("type", isEqualTo: "teacher").limit(10);
query.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
print(doc["name"]);
print(doc["email"]);
});
});
}
body: new FutureBuilder(
future: getTeacher(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return Row(
children: <Widget>[
Expanded(child: Text(snapshot.data["name"])),
Expanded(child: Text(snapshot.data["email"])),
Expanded(
child: IconButton(
icon: Icon(Icons.chat), onPressed: () {}))
],
);
});
}
return Container();
}),
this is the output in the terminal

Your getTeacher()-Function does not return your Future. Because of this
the Nullpointer-Exception ist thrown. You should return query.get() instead of listen to it.
You should also not call getTeacher() in the build-Function because it will be called at every build.
EDIT:
Your method:
Future<DocumentSnapshot> getTeacher() async {
var firebaseUser = await FirebaseAuth.instance.currentUser;
var docRef = FirebaseFirestore.instance.collection("User");
var query = docRef.where("type", isEqualTo: "teacher").limit(1);
return (await query.get()).docs[0];
}
Variable of your widget:
final Future<DocumentSnapshot> teacher = getTeacher();
Your FutureBuilder:
new FutureBuilder(
future: teacher,
builder: ...
)

You are not returning anything from future function. Please check below code
Future<List<DocumentSnapshot>> getTeacher() async {
var firebaseUser = await FirebaseAuth.instance.currentUser;
var docRef = FirebaseFirestore.instance.collection("users");
QuerySnapshot query =
await docRef.where("user_device_language", isEqualTo: "en").limit(10).get();
return query.docs;
}
And handle null value in list view like this
body: new FutureBuilder(
future: getTeacher(),
builder: (BuildContext context, AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data != null) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return Row(
children: <Widget>[
Expanded(child: Text(snapshot.data[index]["name"])),
Expanded(child: Text(snapshot.data[index]["email"])),
Expanded(child: IconButton(icon: Icon(Icons.chat), onPressed: () {}))
],
);
});
} else {
return Text('No Data');
}
}
return Container();
},
),

Related

Flutter Riverpod StreamProvider not waiting for for loop to finish before building Widget

I have a StreamProvider here:
final secondTabProvider = StreamProvider((ref){
EmergencyContactsController contacts = EmergencyContactsController(currentUserID: ref.read(authProvider).currentUser!.uid);
return contacts.getUserEmergencyContacts();
});
And I call it in my build method like so:
_secondTab.when(
data: (data) {
if (!data.exists){
return Text("no data")
}
Map<String, dynamic doc = data.doc() as Map<String, dynamic>;
List conversations = doc['conversation'];
// Store the user profiles
List<EmergencyContactModel> users = [];
for (Map<String, dynamic> user in userConversations){
contacts.getContactInfo(
uid: user['userID']
).then((value){
if (value != null){
EmergencyContactModel contact = EmergencyContactModel.fromJson(value);
contact.messageID = value["id"] + ref.read(authProvider).currentUser!.uid;
users.add(contact);
}
});
}
return Listview.builder(
itemCount: users.length,
itemBuilder: (BuildContext context, int index) => Text(users[index]['name'])
);
},
error: (err, _){
return Text("Error")
},
loading: () => CircularProgressIndicator()
)
The contacts.getContactInfo() method is an async and I need it to execute before the loop continues to the next iteration, but it's not doing that. Any help would be largely appreciated.
I solved it. I converted the for loop into its own async function as seen below:
// Generate a list of users that the current user has had conversations with
userinfoGenerator(List userIDs) async {
// Get the user profiles
List<EmergencyContactModel> users = [];
for (Map<String, dynamic> user in userIDs){
Map<String, dynamic>? contactInfo = await contacts.getContactInfo(uid: user['userID']);
if (contactInfo != null){
EmergencyContactModel contact = EmergencyContactModel.fromJson(contactInfo);
contact.messageID = contactInfo["id"] + ref.read(authProvider).currentUser!.uid;
users.add(contact);
}
}
return users;
}
And then I used a Future Builder to return the result of the function as seen below:
return FutureBuilder(
future: userinfoGenerator(userConversations),
builder: (BuildContext context, AsyncSnapshot snapshot){
// Checking if future is resolved
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: CustomText(
label: '${snapshot.error} occurred',
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
List<String> theName = data[index].name
.split(" ");
return Padding(
padding: const EdgeInsets.only(
top: 12.0),
child: CustomListTile(
contact: data[index],
label: theName.length == 1
? theName[0][0]
: "${theName[0][0]} ${theName[theName
.length - 1][0]}"
),
);
}
);
}
}
return const Center(
child: CircularProgressIndicator(
color: kDefaultBackground,
),
);
},
);

Why when i add data to firebase, the data keep looping?

I'm new to flutter and firebase and I do not know why when I upload any data to firebase the data will keep repeating the same thing but when I hot restart the upload item back to 1, this is my code:
Future getDocId() async {
await FirebaseFirestore.instance
.collection('users')
.orderBy('mobile', descending: true)
.get()
.then(
(snapshot) => snapshot.docs.forEach(
(document) {
print(document.reference);
docIDs.add(document.reference.id);
},
),
);
}
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: FutureBuilder(
future: getDocId(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: docIDs.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: ListTile(
title: ReadUser(documentId: docIDs[index]),
tileColor: Colors.purple[100],
),
);
},
);
},
),
),
This is my future builder
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder: ((context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return Text('Name:' +
'${data['name']}' +
"\n"
'Email:' +
'${data['email']}' +
"\n"
'Mobile Number:' +
'+' +
'${data['mobile']}' +
"");
}
return Text('Loading..');
}),
);
the way you are fetching your data is wrong, instead of pass the result into outside variable you need to return it like this, I assume docIDs is a list of string:
Future<List<String>> getDocId() async {
try {
var snapshot = await FirebaseFirestore.instance
.collection('users')
.orderBy('mobile', descending: true)
.get();
return snapshot.docs.map((document) => document.reference.id).toList();
} catch (e) {
return [];
}
}
then change your FutureBuilder to this:
FutureBuilder<List<String>>(
future: getDocId(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<String> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: ListTile(
title: ReadUser(documentId: data[index]),
tileColor: Colors.purple[100],
),
);
},
);
}
}
},
),

convert this into streambuilder in flutter

I want to convert this function into Streambuilder, but somehow I could not figure out how I could do it. Any help would be greatly appreciated.
Future getReceiverChats() async {
var data = await FirebaseFirestore.instance
.collection("message")
.doc(widget.id)
.collection("nodes")
.orderBy("time", descending: false)
.get();
setState(() {
_msgReceiverList =
List.from(data.docs.map((doc) => Message.fromMap(doc)));
});
}
Try this:
Stream<List<Message>> getReceiverChats(String id) {
return FirebaseFirestore.instance
.collection("message")
.doc(id)
.collection("nodes")
.orderBy("time", descending: false)
.snapshots()
.map((QuerySnapshot query) {
List<Message> dataList = [];
query.docs.forEach((doc) {
dataList
.add(Message.fromMap(doc));
});
return dataList;
});
}
Then:
StreamBuilder<List>(
stream: getReceiverChats(widget.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final List<Message>? dataList = snapshot.data;
if (dataList!.isEmpty) {
return Center(
child: Text('No results'),
);
}
return ListView.builder(
itemCount: dataList.length,
itemBuilder: (context, index) {
return MyWidget(dataList[index]);
});
}
if (snapshot.connectionState == ConnectionState.done) {
if (!snapshot.hasData) {
return Center(
child: Text('No results'),
);
}
}
return const Center(
child: CircularProgressIndicator(),
);
})
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("message")
.doc(widget.id)
.collection("nodes")
.orderBy("time", descending: false)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("Loading...");
default:
return ListView(
children: snapshot.data.docs.map((doc) {
return Message.fromMap(doc);
}).toList(),
);
}
},
),

Flutter fetch nested future

I have a method that fetches a PatientLog from SQLite.However, This PatientLog table mapped to an object with a class named PatientLog. Inside this PatientLog class, several other objects such as Speciality, AttendingPhysician, Course, etc. I need to map these PatienLog records to a local object. However, I have to use nested Futures. I need to retrieve the data from this nested Future. Think of Future of Future.
This is my fetch method
Future<List<Future<PatientLog>>> getForms() async {
Database db = await instance.getDatabase;
List<Map<String, dynamic>> forms =
await db.query(_tablePatientLog, orderBy: 'id DESC');
Institute? institute;
AttendingPhysician? attendingPhysician;
Speciality? speciality;
Course? course;
List<Future<PatientLog>> list = forms.map((myMap) async {
int? courseId = myMap['course_id'] as int?;
int? specialityId = myMap['speciality_id'] as int?;
int? attendingId = myMap['attending_id'] as int?;
int? instituteId = myMap['institute_id'] as int?;
if (courseId != null) {
await getCourse(courseId).then((value) => course=value);
}
if (attendingId != null) {
await getAttending(attendingId).then((value) => attendingPhysician=value);
}
if (specialityId != null) {
await getSpeciality(specialityId).then((value) => speciality=value);
}
if (instituteId != null) {
await getInstitute(instituteId).then((value) => institute=value);
}
return PatientLog.fromMap(
myMap, institute, course, attendingPhysician, speciality);
}).toList();
return list;
}
I need to display that information on a screen. I get an error type 'List<Future<PatientLog>>' is not a subtype of type 'Future<Object?>?'
class _DraftsState extends State<Drafts> {
final SQFLiteHelper _helper = SQFLiteHelper.instance;
#override
void initState() {
super.initState();
_refresh();
}
late List<Future<PatientLog>> fromDatabase;
Future<dynamic> _refresh() async {
await _helper.getForms().then((value) async{
setState(() {
fromDatabase = value;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _helper.getForms(),
builder: (BuildContext context,
AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data!.isEmpty) {
return Center(
child: Text(
"Henüz kaydedilmiş taslak bulunmamaktadır.",
textAlign: TextAlign.center,
style: TEXT_STYLE,
));
}
if (snapshot.hasError) {
return Center(
child: Text(
'Sanırım bir şeyler ters gitti.',
style: TEXT_STYLE,
));
}
if (snapshot.connectionState == ConnectionState.done) {
return RefreshIndicator(
backgroundColor: Colors.grey[700],
color: LIGHT_BUTTON_COLOR,
onRefresh: _refresh,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
shrinkWrap: true,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(
future: snapshot.data,
builder: (context,innerSnap) {
return Text(innerSnap.toString());/*CustomListTile(
formData: innerSnap.data[index],
index: index,
routeTo: 1,
isDeletable: true,
);*/
}
);
},
),
),
);
}
return const Center(
child: Text("Nothing")//spinkit,
);
}),
);
}
}

Avoid StreamBuilder refreshing running SetState in Flutter

I have a page which displays 2 elements, both of them are different StreamBuilder but the second one depends on the first one.
To make it more clear I display this:
Firebase documents (list)
Firebase user
If we sign out both StreamBuilder disappear. That's fine, but my problem comes when I need to select a document from the list:
return ListTile(
leading: FlutterLogo(size: 40.0),
title: Text(set["title"]),
selected: _selected[index],
trailing: Badge(
badgeColor: Colors.grey,
shape: BadgeShape.circle,
toAnimate: true,
onTap: () => setState(() => _selected[index] = !_selected[index]),
);
Everytime I do the SetState() I refresh the first StreamBuilder (not sure why) and with this the second one.
This is the list widget:
Widget _mySetsLists(BuildContext context) {
List<bool> _selected = List.generate(20, (i) => false);
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
FirebaseUser user = snapshot.data;
if (snapshot.hasData) {
return StreamBuilder(
stream: Firestore.instance
.collection('users')
.document(user.uid)
.collection('sets')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot set = snapshot.data.documents[index];
return ListTile(
leading: FlutterLogo(size: 40.0),
title: Text(set["title"]),
selected: _selected[index],
onTap: () => setState(() => _selected[index] = !_selected[index]),
);
},
);
} else {
return Center(
child: new CircularProgressIndicator(),
);
}
},
);
} else {
return Text("loadin");
}
},
);
}
}
And this is the user profile:
class UserProfileState extends State<UserProfile> {
#override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildListDelegate(
[
_mySetsLists(context),
Divider(),
StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user == null) {
return Text('not logged in');
}
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
user.photoUrl,
),
),
title: Text(user.displayName),
subtitle: Text(user.email),
trailing: new IconButton(
icon: new Icon(Icons.exit_to_app),
highlightColor: Colors.pink,
onPressed: () {
authService.signOut();
}),
);
} else {
return Text("loading profile"); // <---- THIS IS WHAT I SEE
}
},
),
],
),
);
}
I also went through the same difficulty, but this is the trick i used
var itemsData = List<dynamic>();
var _documents = List<DocumentSnapshot>();
#override
void initState() {
// TODO: implement initState
super.initState();
getData();
}
getData(){
Firestore.instance
.collection('users')
.document(currentUser.uid)
.collection('set')
.getDocuments()
.then((value) {
value.documents.forEach((result) {
setState(() {
_documents.add(result);
itemsData.add(result.data);
});
});
});
}
replace your listview builder will be like this
ListView.builder(
shrinkWrap: true,
itemCount: _documents.length,
itemBuilder: (context, index) {
return ListTile(
title:Text(itemsData[index]['name'])
)
})
Hope it helps!!
If you pretend to use setstat a lot using the stream you can download the data locally. So every reload will not download data again, but just show the local data.
First step: declare the variable that will store data locally.
QuerySnapshot? querySnapshotGlobal;
Then where you read the streamData, first check if the local data you just declared is empty:
//check if its empty
if(querySnapshotGlobal==null)
//as its empty, we will download it from firestore
StreamBuilder<QuerySnapshot>(
stream: _queryAlunos.snapshots(),
builder: (context, stream){
if (stream.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
else if (stream.hasError) {
return Center(child: Text(stream.error.toString()));
}
else if(stream.connectionState == ConnectionState.active){
//QuerySnapshot? querySnapshot = stream.data;
//instead of save data here, lets save it in the variable we declared
querySnapshotGlobal = stream.data;
return querySnapshotGlobal!.size == 0
? Center(child: Text('Sem alunos nesta turma'),)
: Expanded(
child: ListView.builder(
itemCount: querySnapshotGlobal!.size,
itemBuilder: (context, index){
Map<String, dynamic> map = querySnapshotGlobal!.docs[index].data();
//let it build
return _listDeAlunoswid(map, querySnapshotGlobal!.docs[index].id);
},
),
);
}
return CircularProgressIndicator();
},
)
else
//now, if you call setstate, as the variable with the data is not empty, will call it from here e instead of download it again from firestore, will load the local data
Expanded(
child: ListView.builder(
itemCount: querySnapshotGlobal!.size,
itemBuilder: (context, index){
Map<String, dynamic> map = querySnapshotGlobal!.docs[index].data();
return _listDeAlunoswid(map, querySnapshotGlobal!.docs[index].id);
},
),
),
Hope it helps you save some money!