I want to check if the firestore document with a specific id exists. Actually, my idea is to store and fetch user-specific data from firestore. If there is no user-specific data uploaded it will show "no data to show" in the app. Till now what I have done is adding data to firestore with document id equals to the current user's UID.
FirebaseFirestore.instance.collection("doc_folder").doc(currentuser!.uid).set(data);
now I am unable to check if the firestore database contains any document with this data. By far, I have reached:
class Crud {
getData() {
return FirebaseFirestore.instance
.collection('doc_folder')
.where("userId", isEqualTo: currentUser!.uid)
.get();
}
}
#override
void initState() {
crud.getData().then((result) {
snap = result;
setState(() {});
});
super.initState();
}
Widget build(BuildContext context){
return Container(
snap != null? //if code
: //else code
)
}
The above code returns "document exists" even if the data does not exist with the current user's UID.
The following line from your code returns a QuerySnapshot which is not nullable:
FirebaseFirestore.instance.collection('doc_folder').where("userId", isEqualTo: currentUser!.uid).get()
and you assign the returned value of QuerySnapshot to snap and perform the following:
snap != null ? ... : ...
However, this condition will be true regardless of whether a document exists.
You should instead check docs property from QuerySnapshot, which is a list of the document snapshots returned for your query.
I'd prefer the widget of FutureBuilder over calling getData() in initState for this example to make it clear:
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future: crud.getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.docs.isNotEmpty) {
// Document exists
} else {
// Document does not exist
}
} else {
// Show a loading widget
// ...
}
},
);
}
Full Example
class Crud {
Future<QuerySnapshot> getData() async {
return await FirebaseFirestore.instance
.collection('doc_folder')
.where("userId", isEqualTo: currentUser!.uid)
.get();
}
}
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
final crud = Crud();
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future: crud.getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final querySnaphost = snapshot.data; // Get query snapshot
if (querySnaphost.docs.isNotEmpty) {
// Document exists
final documentSnapshot =
querySnaphost.docs.first; // Get document snapshot
return Text('${documentSnapshot.data()}'); // Get the data
} else {
// Document does not exist
return Text('Document does not exist.');
}
} else {
// Show a loading widget
return CircularProgressIndicator();
}
},
);
}
}
The reason is that value of snapshot is not null even though document doesn't exists. So use below code:-
QuerySnapshot snap=await FirebaseFirestore.instance.collection('doc_folder').where("UserId", isEqualTo: currentuser!.uid).get();
if(snap.docs.isNotEmpty)
DocumentSnapshot doc=snap.docs.first;
print(doc['username']);//like this you can access data
else{
print("Doc doesn't exits");
}
You can do that
var
doc=FirebaseFirestore.instance
.collection('doc_folder')
.where("userId", isEqualTo:
currentUser!.uid)
.get();
if(doc.exist){
print('exist');
}
There is a slight problem with your code. build() is called while async crud.getData() is still running. Therefore snap will be have its default value. If snap's default value is not null, then snap != null will be true and you might assume your snap has its intended value.
Full working code
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Crud crud = Crud();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
future: crud.getData(),
// above is called everytime the widget is rebuilt which is not optimal
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.hasError) return Text('Something went wrong');
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
if (snapshot.data.docs.isNotEmpty) {
print('document exists');
print(snapshot.data.docs.map((e) => e.data()));
return Container();
} else {
print('document does not exist');
return Container();
}
},
),
);
}
}
class Crud {
Future<QuerySnapshot<Map<String, dynamic>>> getData() {
return FirebaseFirestore.instance
.collection('users')
.where("userId", isEqualTo: 'c1fG8zo0OWgHsPClEKWN')
.get();
}
}
FirebaseFirestore.instance
.collection('Company')
.doc('1235')
.get()
.then((value) {
if (value.exists) {
// Do something Here
}
The question asked to check if the document exists, and the answer accepted only works if docId is stored as field in that doc. Needs Review.
Related
Retrieving shopping cart items my snapshot data is inconsistent. When one item is in cart I get this correctly formatted result:
{1111111111111: 1, PriceSmart: 540.0}
When two items in cart, and second item is also "PriceSmart", I get an error because returns this result:
{1111111111111: 1, PriceSmart: 300.0, 5555555555555: 1}
and should be:
{1111111111111: 1, PriceSmart: 540.0, 5555555555555: 1, PriceSmart: 300.0}
This is my firebase data structure:
First cart item:
Second cart item:
Basically is combining the "seller" (PriceSmart), when I need to return complete data from each cart item, otherwise I get an error as soon as I have more than one item in cart and seller is the same.
Please check the Stream in my code and see what is wrong with this implementation:
class PriceUpdaterWidget extends StatefulWidget {
const PriceUpdaterWidget({
Key? key,
required this.loginService,
required this.code,
required this.itemSubCategory,
}) : super(key: key);
final LoginService loginService;
final String? code;
final SubCategory? itemSubCategory;
_PriceUpdaterWidgetState createState() => _PriceUpdaterWidgetState();
}
class _PriceUpdaterWidgetState extends State<PriceUpdaterWidget> {
#override
Widget build(BuildContext context) {
CategorySelectionService catSelection =
Provider.of<CategorySelectionService>(context, listen: false);
Stream<DocumentSnapshot> priceDocStream = FirebaseFirestore.instance
.collection('shoppers')
.doc(widget.loginService.loggedInUserModel!.uid)
.collection("cartItems")
.doc(widget.code)
.snapshots();
return StreamBuilder<DocumentSnapshot>(
stream: priceDocStream,
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
SellerNameService isSellerName =
Provider.of<SellerNameService>(context, listen: false);
var sellerName = isSellerName.isSellerName;
if (snapshot.data != null) {
return Text(
snapshot.data![sellerName].toStringAsFixed(2),
textAlign: TextAlign.center,
);
} else {
return Text('No Data');
}
});
}
}
Without seeing more of your code, it's hard to make an example to exactly fit your specification, and the error you're getting doesn't match the code you posted, but, broadly, you need to:
move the Stream outside your build function and into initState
process each snapshot one at a time
stay away from processing the data in your build widget
class PriceUpdaterWidget extends StatefulWidget {
final String login, code;
const PriceUpdaterWidget(this.login, this.code);
#override
_PriceUpdaterWidgetState createState() => _PriceUpdaterWidgetState ();
}
class _PriceUpdaterWidgetState extends State<PriceUpdaterWidget> {
Stream<DocumentSnapshot> priceStream; // only one stream per widget
#override
void initState() {
super.initState();
priceStream = FirebaseFirestore.instance // set the stream once
.collection("shoppers")
.doc(widget.login)
.collection("cartItems")
.doc(widget.code)
.snapshots();
}
#override
Widget build(BuildContext context) => StreamBuilder<DocumentSnapshot>(
stream: priceStream,
builder: (context, snapshot) {
const String sellerName = "PriceSmart";
return snapshot.data == null
? const Text("No data")
: Text(
snapshot.data[sellerName].toStringAsFixed(2),
textAlign: TextAlign.center,
);
}
);
}
In my Firestore DB, inside 'location' collection i have 2 docs,(named as Europe,Australia) having a field 'name' with their string values (same as their document names).
I have worked with StreamBuilder and Streams before, but this time i dont want real-time calls, but just once.
I wanna print that 'name' field data of all the docs inside location collection.
This is what my UI code looks like:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double spaceInBetween = 25;
#override
Widget build(BuildContext context) {
DatabaseService().getData();
return Scaffold(
body: Container(
child: Text("data here")
);
}
I wanna print all that documents data, with all their names using ListView.builder() on the HomePage.
This is my DatabaseService class (using the official FlutterFire Docs https://firebase.flutter.dev/docs/firestore/usage/ but didnt find what i was looking for)
class DatabaseService {
final locationCollection = FirebaseFirestore.instance.collection("location");
getData() async {
await locationCollection.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
print(doc['name']);
});
});
}
}
Also wanted to know if there's any other way i could do this, using FutureBuilder or anything else, just wanna get field data from all docs in a collection from Firestore and print it (im still learning).
Thank you :)
I think the answer is FutureBuilder. You can create a Future method which is going to get datas from Firebase servers and return it. After that you just create a FutureBuilder which is going to help you to show datas and if something wrong with the server or the internet connection you will not get any error messages because FutureBuilder will show an CircularProgressIndicator.
I made a demo code for you to demostrate FutureBuilder.
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final locationCollection = FirebaseFirestore.instance.collection("location");
#override
void initState() {
super.initState();
}
Future<List<String>> getData() async {
List<String> name = [];
await locationCollection.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
name = doc['name'];
});
});
return name;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FutureBuilder<List<String>>(
future: getData(), // call getData method
builder: (context, snapshot) {
List<String> nameList = snapshot.data ?? []; // create a local variable which is storing data from the getData method
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListView.builder( // if getData method give datas listviewbuilder is going to show datas
itemCount: nameList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(nameList[index]),
);
},
)
: Center(child: CircularProgressIndicator()); // if something wrong with the server or with the internet you will see a CircularProgressIndicator
}),
),
),
);
}
}
In order to ensure you only get the data once, you can use a FutureBuilder and ensure you define the future outside the build method (for example in the initState) so that it doesn't get called again whenever the build method is called.
FutureBuilder
...
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateWidget, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Update the getData method of your DatabaseService class to this below:
Future<List<String>> getData() async {
final QuerySnapshot locationDataSnapshot = await locationCollection.get();
final List<String> listOfNames = locationDataSnapshot.docs
.map((QueryDocumentSnapshot documentSnapshot) =>
documentSnapshot.data()['name'] as String)
.toList();
return listOfNames;
}
This code above fetches the list of documents from the location collection and maps them to a list of names, which is then returned.
You can then get define the future object to get this data in your initState and use it in your FutureBuilder like shown below:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _nameListFuture;
#override
void initState() {
super.initState();
_nameListFuture = DatabaseService().getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<String>>(
future: _nameListFuture,
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
}
final List<String> nameList = snapshot.data;
return ListView.builder(
itemCount: nameList.length,
itemBuilder: (context, index) => Text(nameList[index]),
);
},
),
);
}
}
I want to get a string from my DB in Firebase, I'm very confused and I don't know how to do that!
I made a big search in the few past days about this idea but unf I don't get any useful result
what do I want? I want to make a Method that returns the 'Question' string.
DB:Collection / History/question
thank you for your time
the incorrect code :
Future loadData() async {
await Firebase.initializeApp();
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text("Error: ${snapshot.error}"),
),
);
}
// Collection Data ready to display
if (snapshot.connectionState == ConnectionState.done) {
// Display the data inside a list view
return snapshot.data.docs.map(
(document) {
return method(
document.data()['question'].toString().toString(),
); //Center(
},
);
}
}
Here is the official documentation from Flutter Fire - https://firebase.flutter.dev/docs/firestore/usage/
Read data from Cloud firestore
Cloud Firestore gives you the ability to read the value of a collection or a document. This can be a one-time read or provided by real-time updates when the data within a query changes.
One-time Read
To read a collection or document once, call the Query.get or DocumentReference.get methods. In the below example a FutureBuilder is used to help manage the state of the request:
class GetUserName extends StatelessWidget {
final String documentId;
GetUserName(this.documentId);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data.exists) {
return Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data.data();
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
}
}
To learn more about reading data whilst offline, view the Access Data Offline documentation.
Realtime changes
FlutterFire provides support for dealing with real-time changes to collections and documents. A new event is provided on the initial request, and any subsequent changes to collection/document whenever a change occurs (modification, deleted, or added).
Both the CollectionReference & DocumentReference provide a snapshots() method which returns a Stream:
Stream collectionStream = FirebaseFirestore.instance.collection('users').snapshots();
Stream documentStream = FirebaseFirestore.instance.collection('users').doc('ABC123').snapshots();
Once returned, you can subscribe to updates via the listen() method. The below example uses a StreamBuilder which helps automatically manage the streams state and disposal of the stream when it's no longer used within your app:
class UserInformation extends StatefulWidget {
#override
_UserInformationState createState() => _UserInformationState();
}
class _UserInformationState extends State<UserInformation> {
final Stream<QuerySnapshot> _usersStream = FirebaseFirestore.instance.collection('users').snapshots();
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document.data()['full_name']),
subtitle: new Text(document.data()['company']),
);
}).toList(),
);
},
);
}
}
By default, listeners do not update if there is a change that only affects the metadata. If you want to receive events when the document or query metadata changes, you can pass includeMetadataChanges to the snapshots method:
FirebaseFirestore.instance
.collection('users')
.snapshots(includeMetadataChanges: true)
I'm having problem in getting data from firestore.I can see all the document exist in firestore collection.But it returns empty list inside stream builder. Here is my code:
// Chat Screen.Dart
class ChatScreen extends StatefulWidget {
static final routeName = "/chat";
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Inbox"),),
body: FutureBuilder<String>(
future: DatabaseService.instance.getUserId(),
builder: (context, idSnap) {
if (idSnap.connectionState == ConnectionState.done) {
print("id snap :${idSnap.data}");
return StreamBuilder<QuerySnapshot>(
stream: DatabaseService.instance.chatListStream(idSnap.data),
builder: (context, chatsSnap) {
if (chatsSnap.connectionState == ConnectionState.waiting) {
return Text("waiting for the data");
} else if (chatsSnap.hasError) {
return Text('error ${chatsSnap.error}');
} else {
print("has data");
print("document found: ${chatsSnap.data.docs.length}"); // it prints 0
return Text("data is loaded");
}
});
} else {
return Container();
}
}
),
);
}
// DatabaseService.dart
class DatabaseService{
DatabaseService._privateConstructor();
static final DatabaseService instance = DatabaseService._privateConstructor();
FirebaseFirestore firestore = FirebaseFirestore.instance;
Future<String>getUserId() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userId = prefs.getString('user_id') ?? '0';
return userId;
}
Stream<QuerySnapshot>chatListStream(String userId){
return firestore.collection('users').doc(userId).collection('chatList').snapshots();
}
}
I have checked The collection and document title which is correct and the userId being passed is not null and valid data.Here is the Screenshot of firestore database :
What am I doing wrong here?
Look at how the documents in the chatList subcollection(s) are displayed in an italic font: this means that, in the console, these documents are only present as "container" (or "placeholder") of one or more sub-collection(s) but that they are not "genuine" documents, i.e. they don't exist in the Firestore database. Therefore querying documents in one of these chatList subcollections will return an empty QuerySnapshot.
You have probably created documents directly under a sub-collection of each of these chatList documents, without creating the chatList documents themselves.
So, in your case, if you need to have genuine docs in the chatList collections, you need to create them at the same time you create the first subcollection doc.
Also, note that Cloud Firestore has shallow reads: querying for documents in a collection doesn't pull in data from subcollections.
I am trying to build a flutter app with Firestore .
I am trying to write a code wherein if a document if exists in Firestore in a collection then the user goes to a new screen if not he goes to an other screen
FirebaseAuth auth = FirebaseAuth.instance;
class check extends StatelessWidget {
static const String routeName = '/checkif';
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
final snapshot = fb.collection("Profile").doc(firebaseUser.uid).get();
if (snapshot == null) {
return addparentcompany();
} else{
return homepage();}
}
}
Even if the snapshot is null even then this gets routed to homepage instead of parent company
Because it takes time to fetch the data, You will have to wait for the data while its being retrieve.. So for that you'll have to use the FutureBuilder
body: FutureBuilder(
future: fb.collection("Profile").doc(firebaseUser.uid).get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return addparentcompany();
} else {
if (snapshot.data.data() == null) {
return Center(
child: Text('An error occured'),
);
} else return homepage();
}
},
),