I am trying to get all the documents with their fields from firestore collection, but it's not working. I did this:
final _fireStore = FirebaseFirestore.instance;
Future<void> getData() async{
QuerySnapshot querySnapshot = await _fireStore.collection('addsaidas').get();;
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
}
But the screen still is empty. The code is running, but nothing appears.
Your code seems fine to me at first glance. For issue regarding this line
But the screen still is empty. The code is running, but nothing appears.
To display the result on screen we have to use Widgets provided by flutter.
Here’s one Example with using ListView and also printing the result in the console onPress of the FloatingActionButton :
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
final _fireStore = FirebaseFirestore.instance;
final ref =
FirebaseFirestore.instance.collection('addsaidas').snapshots();
Future<void> getData() async {
QuerySnapshot querySnapshot =
await _fireStore.collection('addsaidas').get();
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
for (var dataMap in allData) {
if (dataMap is Map) {
// add a type check to ensure dataMap is a Map
for (var key in dataMap.keys) {
print('$key: ${dataMap[key]}'); //printing document fields using keys
}
print('----------------------');
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My Screen')),
body: StreamBuilder<QuerySnapshot>(
stream: ref,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final documents = snapshot.data!.docs;
return ListView.builder(
itemCount: documents.length,
itemBuilder: (context, index) {
final document = documents[index];
final data = document.data() as Map<String, dynamic>;
return ListTile(
title: Text(data['nomesaida']),
subtitle: Text(data['datasaida']),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: getData,
backgroundColor: Colors.green,
child: const Icon(Icons.navigation),
),
);
}
}
You can print other fields as above mentioned.
Related
I'm adding data from Firestore to a Stream from StreamBuilder, but I'm getting the following error:
Exception has occurred. StateError (Bad state: Snapshot has neither data nor error
My code.
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
AppState? estado;
static String? userID = FirebaseAuth.instance.currentUser?.uid;
static final userColeccion = FirebaseFirestore.instance.collection("users");
var groupfav = ' ';
Stream<QuerySnapshot>? taskGroup;
#override
void initState() {
super.initState();
getGroupFavData();
}
void getGroupFavData() async {
var groupFavData = await userColeccion.doc("$userID").get();
var groupfav = groupFavData.data()!['groupfav'];
taskGroup = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("task")
.snapshots();
}
#override
Widget build(BuildContext context) {
estado = Provider.of<AppState>(context, listen: true);
return Scaffold(
appBar: AppBar(
title: const Text("Home"),
automaticallyImplyLeading: false,
),
body: StreamBuilder(
stream: taskGroup,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return const Text("error");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
var data = snapshot.requireData;
return ListView.builder(
itemCount: data.size,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text("${data.docs[index]['titulo']}"),
subtitle: Text("${data.docs[index]['contenido']}"),
onTap: () {},
trailing: IconButton(
icon: const Icon(Icons.delete),
color: Colors.red[200],
onPressed: () {},
),
),
);
},
);
},
),
);
}
}
Ok, looking at your issue, I see that 1) you need to get the data of the document BEFORE you start listening on that document, which is normal, so you want to do a call first to the collection, get the document, then listen on the document's collection called task, which makes sense. Your issue is still an asynchronous issue. The app is rebuilding on a stream that still hasn't arrived; you have to fix the sequence of things.
You then need to switch things up a bit and do the following:
Option #1:
a) Use a FutureBuilder: this will allow you to make the async call to get the document name based on the user Id
b) After you get the document associated to that user, you want to listen on the stream produced by the collection called tasks in that document. There is where then you can hook up the StreamBuilder.
Option #2:
a) Keep things the way you have, but do a listen on the taskGroup snapshots; but keep rebuilding the list as the values arrive on that collection.
Those are my suggestions.
Here's some brief code on option 1:
// .. in your Scaffold's body:
Scaffold(
body: FutureBuilder( // the future builder fetches the initial data
future: userColeccion.doc("$userID").get(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
var groupfav = snapshot.data()!['groupfav'];
// then once the 'groupfav' has arrived,
// start listening on the taskGroup
taskGroup = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("task")
.snapshots();
return StreamBuilder(
stream: taskGroup,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
// the rest of your code
});
}
return CircularProgressIndicator();
}
)
)
Option 2 would be something like:
List<Task> userTasks = [];
void getGroupFavData() async {
var groupFavData = await userColeccion.doc("$userID").get();
var groupfav = groupFavData.data()!['groupfav'];
taskGroup = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("task")
.snapshots().listen((snapshot) {
// here populate a list of your tasks
// and trigger a widget rebuild once you've grabbed the values
// and display it as a list on the UI
setState(() {
userTasks = snapshot.docs.map((d) => Task.fromJson(d.data())).toList();
});
});
}
And in your Scaffold, you can have a ListView just rendering the items on that task list, like:
ListView.builder(
itemCount: userTasks.length,
itemBuilder: (context, index) {
// render your tasks here
})
Here's a Gist with some working code to illustrate my point. Run it on DartPad and you'll see how using a FutureBuilder wrapping a StreamBuilder will accomplish what you want.
If you run the above code on DartPad, you'll get the following output:
Hope those pointers take you somewhere.
I'm accessing a user's favorite group which is inside groupfav in Firestore, when I get it I want to give it as part of the reference to the streambuilder stream:, so that it knows what to show in a list, but I can't pass the variable that contains the favorite group, what should I do or what am I doing wrong?
static String? userID = FirebaseAuth.instance.currentUser?.uid; // get current user id
static var taskColeccion = FirebaseFirestore.instance.collection("usuarios");
var tack = taskColeccion.doc("$userID").get().then((value) {
var groupfav = value.data()!["groupfav"]; // value i get from firestore
return groupfav;
});
late Stream<QuerySnapshot> task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
photo of firestore
The photo shows how Firestore's logic is and the value marked in green is what I must pass to the late Stream<QuerySnapshot> task... in its reference, logically it is a random value that I would not know. thanks for any help!
this is what the code looks like now (I took things that were not important)
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
static String? userID = FirebaseAuth.instance.currentUser?.uid;
static final taskColeccion =
FirebaseFirestore.instance.collection("usuarios");
String groupfav = '';
final tack = taskColeccion.doc("$userID").get().then((value) {
groupfav = value.data()!["groupfav"];
return groupfav;
});
Stream<QuerySnapshot> task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home"),
automaticallyImplyLeading: false,
),
body: StreamBuilder(
stream: task,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return const Text("error");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("cargando");
}
final data = snapshot.requireData;
return ListView.builder(
itemCount: data.size,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text("${data.docs[index]['titulo']}"),
subtitle: Text("${data.docs[index]['contenido']}"),
onTap: () {},
trailing: IconButton(
icon: const Icon(Icons.delete),
color: Colors.red[200],
onPressed: () {
// delete function
},
),
),
);
},
);
},
),
);
}
}
You just need to declare groupfav outside of the scope of the get method of taskColeccion;
The way you have it, the variable no longer exists by the time you're trying to pass it into the task stream.
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
static String? userID = FirebaseAuth.instance.currentUser?.uid;
static final taskColeccion =
FirebaseFirestore.instance.collection("usuarios");
String groupfav = '';
late Stream<QuerySnapshot> task;
#override
void initState() {
super.initState();
taskColeccion.doc("$userID").get().then((value) {
groupfav = value.data()!["groupfav"];
return groupfav;
});
task = FirebaseFirestore.instance
.collection("groups")
.doc(groupfav) // pass the obtained value
.collection("tareas")
.snapshots();
}
I am trying to get data (In real-time) from my database using QuerySnapshot.
and I need to update my data as well. This is my method and I don't know how to get my documentID in this method.
class LoadData extends StatefulWidget {
const LoadData({Key? key}) : super(key: key);
#override
_LoadDataState createState() => _LoadDataState();
}
class _LoadDataState extends State<LoadData> {
//Read Data in realTime Snapshot
final Stream<QuerySnapshot> _cupCakeStream = FirebaseFirestore.instance
.collection('cupcake')
.snapshots(includeMetadataChanges: true);
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _cupCakeStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
shrinkWrap: true,
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
print(data); //**Print collection only..I need to get documentID with each collection**
return ListTile(
title: Text(data['cupcake_name']),
subtitle: Text(data['description']),
);
}).toList(),
);
},
);
}
}
document is a DocumentSnapshot and has an id property.
So, the following should do the trick:
return ListView(
shrinkWrap: true,
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
print(data);
print(document.id); // <= Should print the doc id
return ListTile(
title: Text(data['cupcake_name']),
subtitle: Text(data['description']),
);
}).toList(),
);
I have an app that is connected to Firebase, and I am making a screen that will show all notifications sent, for that I am using SharedPreferences. But as the notification arrives on a map, I am placing it inside a List <Map <dynamic, dynamic >>, to show the notification.
String title, body;
Map<dynamic, dynamic> notific;
List<Map<dynamic, dynamic>> notifList = [];
///Widget
return Scaffold(
extendBody: true,
backgroundColor: widget._colors.white,
appBar: appBar,
body: ListView.builder(
itemCount: notifList.length,
itemBuilder: (context, i) {
return Card(
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
title: Text(
notifList.elementAt(i)['title'],
),
subtitle: Text(
notifList.elementAt(i)['body'],
),
),
);
},
),
);
}
Firebase Method
Future<dynamic> fcmMessageReceiver() async {
FirebaseMessaging.instance.getInitialMessage().then((value) {
if (value != null) {}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
notific = {
'title': message.notification.title,
'body': message.notification.body
};
notifList.add(notific);
setState(() {
title = message.notification.title;
body = message.notification.body;
});
print('MENSAGEM: $notific');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {});
}
Shared Preferences method, being called on initState()
void savePush() async {
SharedPreferences sharePref = await SharedPreferences.getInstance();
final strList = sharePref.getStringList('push')??[];
sharePref.setStringList('push', notifList.toString());
}
My question is, how can I keep these notifications, so whenever I want to see them, I can get them easily, and set up the Cards with the notifications?
So, there are many approaches to solve this issue, some of my approaches will be to convert each message to a JSON encoded string and then push it to the sharedPreference.setStringList(list). Another way is to make the whole list a JSON encoded string and save it to SharedPreferences like a string by calling sharedPreference.setString(list).
Let's say your List of the message is like this:
List<Map<String, dynamic>> messagesForUI = [];
And, you've initialized SharedPreferences and previous messages from SharedPreferences like this:
SharedPreferences sharedPreference = await SharedPreferences.getInstance();
List<String> sharedPreferenceMessages = [];
Now, to retrieve all your previous messages from SharedPreferences and then set the previous messages to messagesForUI inside the initState method, you can do this:
sharedPreferenceMessages = sharedPreference.getStringList("messages") ?? [];
sharedPreferenceMessages.forEach((element) {
Map<String, dynamic> messageMap = Map<String, dynamic>.from(json.decode(element));
messagesForUI.add(messageMap);
});
Now, you've your list ready to roll.
Let's say you have a new message from FCM and you want to save it to the SharedPreferences. Let's save the new message this way:
Map<String, dynamic> newMessage = Map<String, dynamic>.from(fcmMessage);
setState((){
messagesForUI.add(newMessage);
});
String newMessageJson = json.encode(newMessage);
sharedPreferenceMessages.add(newMessageJson);
sharedPreference.setStringList("messages", sharedPreferenceMessages);
There you go. You can also save messages to SharedPreferences via calling sharedPreference.setString(map), just like this approach. If need a demonstration of that process, just comment here.
Sample code:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class NotificationRoute extends StatefulWidget {
#override
_NotificationRouteState createState() => _NotificationRouteState();
}
class _NotificationRouteState extends State<NotificationRoute> {
List<Map<String, dynamic>> messagesForUI = [];
List<String> sharedPreferenceMessages = [];
SharedPreferences sharedPreference;
#override
void initState() {
init();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: messagesForUI.isEmpty
? Center(
child: Text("No notifications"),
)
: ListView.builder(
itemBuilder: (context, index) {
final Map<String, dynamic> message = messagesForUI[index];
return ListTile(
title: Text(message["title"]),
subtitle: Text(message["body"]),
);
},
shrinkWrap: true,
physics: ScrollPhysics(),
scrollDirection: Axis.vertical,
itemCount: messagesForUI.length,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Map<String, dynamic> newMessage = {"title": "test title", "body": "test body"};
setState(() {
messagesForUI.add(newMessage);
});
String newMessageJson = json.encode(newMessage);
sharedPreferenceMessages.add(newMessageJson);
sharedPreference.setStringList("messages", sharedPreferenceMessages);
},
child: Icon(Icons.add),
),
);
}
init() async {
sharedPreference = await SharedPreferences.getInstance();
sharedPreferenceMessages = sharedPreference.getStringList("messages") ?? [];
sharedPreferenceMessages.forEach((element) {
Map<String, dynamic> messageMap = Map<String, dynamic>.from(json.decode(element));
messagesForUI.add(messageMap);
});
}
}
Now, as I don't have any FCM set-up on my project, I just replicate the message add process to the SharedPreference via FloatingActionButton.
Happy coding :D
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();