flutter Firestore data check and navigate to a new screen - firebase

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();
}
},
),

Related

Flutter firestore check if document with specific id exists

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.

flutter firebase get string from database using future

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)

How to retrieve a Firebase Storage image stream in flutter?

I've got a few photo's I've uploaded into my firebase storage under a file called 'photos' and I want to be able to retrieve them onto my app through a stream. I have done this before through Firebase cloud database by tapping into the Firestore.instance.collection('messages').snapshots() property in my StreamBuilder, but I don't know how to access the firebase storage snapshots and upload them as a stream into my app.
This was my code for the messages snapshot, I hope it helps:
final _firestore = Firestore.instance;
void messagesStream() async {
await for (var message in _firestore.collection('messages').snapshots()){
for (var snapshot in message.documents){
print(snapshot.data);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('messages').snapshots(),
builder: (context, snapshot){
if (!snapshot.hasData){
return Center(
child: CircularProgressIndicator(backgroundColor: Colors.lightBlueAccent,),
);
} else {
final messages = snapshot.data.documents;
List<Text> messageWidgets = [];
for (var message in messages){
final messageText = message.data['text'];
final messageSender = message.data['sender'];
final messageWidget = Text('$messageText from $messageSender');
messageWidgets.add(messageWidget);
}
return Column(children: messageWidgets,);
}
}
),
),
},
So I figured out you can't create a stream from the firebase storage, but what I could do was, in my firebase cloud database, start a new collection called 'my_collection' and in a new document, create an auto-ID, with a field called 'image' which is a string, with an http reference to an image that is on the internet, or one you can upload to the internet (this is what I did on imgur.com, credit to them)! Here is my code below, I hope it helps others! If it doesn't, have a look at this code written by iampawan, he helped me a tonne!
https://github.com/iampawan/FlutterWithFirebase
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
StreamSubscription<QuerySnapshot> subscription;
List <DocumentSnapshot> myList;
final CollectionReference collectionReference = Firestore.instance.collection('my_collection');
final DocumentReference documentReference = Firestore.instance.collection('my_collection').document('GFWRerw45DW5GB54p');
#override
void initState() {
super.initState();
subscription = collectionReference.snapshots().listen((datasnapshot) {
setState(() {
myList = datasnapshot.documents;
});
});
}
#override
void dispose() {
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return myList != null ?
ListView.builder(
itemCount: myList.length,
itemBuilder: (context, index){
String imgPath = myList[index].data['image'];
return MyCard(assetImage: Image.network(imgPath), function:
(){
if (imgPath == myList[0].data['image']){
Navigator.pushNamed(context, MyMenu.id);
} else if (imgPath == myList[1].data['image']){
Navigator.pushNamed(context, YourMenu.id);
} else if (imgPath == myList[2].data['image']){
Navigator.pushNamed(context, HisMenu.id);
} else if (imgPath == myList[3].data['image']){
Navigator.pushNamed(context, HerMenu.id);
}
},);
})
: Center(child: CircularProgressIndicator(),
);
}
}
Just to note, MyCard is it's own page with it's own constructor that requires an assetImage and a function for the user to be pushed to a new screen:
MyCard({#required this.assetImage, #required this.function});
final Image assetImage;
final Function function;

Flutter Firebase: StreamBuilder Not Listening to Changes?

I'm trying to create a UI where the user submits a "comment" on each "article". As you can see below, in my code, I get username of the user from Firestore and then checks if this user already has a comment for the respective article.
If the comment exists, it returns a stream of all the comments for that given article. If it does not exist, I return the CommentCollector that is a widget to collect the comment and post it to Firestore. This overall works, expect when I submit a comment via CommentCollector, the UserPostGetter widget does not does not rebuild.
How can I trigger this rebuild? I thought using StreamBuilder to listen to see if there is a comment would be enough, but clearly not so. What am I missing?
Really appreciate the help.
class UserPostGetter extends StatelessWidget {
final String articleId;
final String articleHeader;
UserPostGetter({this.articleId, this.articleHeader});
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: new Container(
child: new FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
String userNumber = snapshot.data.uid;
return new FutureBuilder(
future: getUser(userNumber),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot?.data == null)
return new Center(
child: new Text("Loading..."),
);
String username = snapshot.data.username.toString();
return StreamBuilder(
stream: doesNameAlreadyExist(articleId, username),
builder: (context, AsyncSnapshot<bool> result) {
if (!result.hasData)
return Container(); // future still needs to be finished (loading)
if (result
.data) // result.data is the returned bool from doesNameAlreadyExists
return PostGetter(
articleId: articleId,
);
else
return CommentCollector(
articleID: articleId,
userName: username,
articleTitle: articleHeader,
);
},
);
},
);
} else {
return new Text('Loading...');
}
},
),
),
);
}
}
Stream<bool> doesNameAlreadyExist(String article, String name) async* {
final QuerySnapshot result = await Firestore.instance
.collection('post')
.where('article', isEqualTo: article)
.where('author', isEqualTo: name)
.getDocuments();
final List<DocumentSnapshot> documents = result.documents;
yield documents.length == 1;
}
Future<User> getUser(idNumber) {
return Firestore.instance
.collection('user')
.document('$idNumber')
.get()
.then((snapshot) {
try {
return User.fromSnapshot(snapshot);
} catch (e) {
print(e);
return null;
}
});
}
class User {
String email;
String user_id;
String username;
User.fromSnapshot(DocumentSnapshot snapshot)
: email = snapshot['email'],
user_id = snapshot['user_id'],
username = snapshot['username'];
}

Flutter: Question about async function to retrieve Firebase user ID

App Flowchart
I have a question about async function in flutter. I write an that use Firebase authentication. I want to make it such that the app will read the Firebase User ID at the top level of the app(Root Page in this case) at the init state function and then pass the user object to its child widget. Since the function to retrieve the user ID is an async function, I run into problem that the child widget get a null value for user ID even though it should not be null. I have already use future builder in the children widget but it doesn't work. Does anyone know how to do it correctly.
The exact error I am getting is "A build function returned null. The offending widget is: FutureBuilder. Build functions must never return null."
RootPage (Parent)
class _RootPageState extends State {
AuthStatus authStatus = AuthStatus.notSignIn;
String cuerrentUserId;
#override
void initState() {
super.initState();
widget.auth.currentUser().then((userId) {
setState(() {
authStatus = userId == null ? AuthStatus.notSignIn : AuthStatus.signIn;
cuerrentUserId = userId;
});
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
switch(authStatus) {
case AuthStatus.notSignIn:
return new LoginPage(
auth: new Auth(),
onSignedIn: _signedIn,
);
case AuthStatus.signIn:
if (snapshot.connectionState == ConnectionState.done) {
return new HomePage(
auth: widget.auth,
onSignedOut: _signedOut,
userId: snapshot.data.uid,
);
}
else {
}
}
}
),
);
}
HomePage (child)
Future<String> setUserData() async {
currentUser = User(widget.userId);
await currentUser.loadUserData();
_userName = currentUser.name;
_userEmail = currentUser.email;
_userPicURL = currentUser.avatar;
print('current user');
print(currentUser.id);
print(currentUser.email);
return _userName;
}
#override
Widget build(BuildContext context) {
return UserProvider(
user: currentUser,
child: new Container(
child: new FutureBuilder(
future: setUserData(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.data!=null) {
...
You could make your main function async in order to decide during app launch if you should show the login or home page as the first screen.
This could look like the following:
Future<void> main() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
bool showHomePage = currentUser != null;
runApp(MyApp(showHomePage));
}
You could use the showHomePage param inside MyApp now to determine which screen should be shown initially. That's it.
Bonus: With this approach you also don't need to show a screen for a friction of a second which may be replaced by another one (e.g. show the home screen --> user is not logged in --> replace with login screen). This could look like a glitch in your app.

Resources