I'm trying to add a check after a user is authenticated if a user has a document in Firestore under a 'users' collection based on their UserID ('uid') via a StreamBuilder.
The issue I have is when I run my code, it works as intended but after a few seconds, it redirects to the 'UserHomeScreen' even if the document does not exist. How can I rectify this so a user without a user document does not get pushed to my 'UserHomeScreen'?
Here is my code:
class UserStream extends StatelessWidget {
const UserStream({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance.collection('users').doc('uid').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return const UserHomeScreen();
} else {
return const SignUpNewUser();
}
},
);
}
}
The snapshot.hasData is true when the asynchronous call has completed. Even when the document doesn't exist, snapshot.hasData will still be true once that has been determined.
To ensure the document exists, you'll also want to check:
if (snapshot.hasData && snapshot.data!.exists) {
...
This is also shown in the documentation on handling one time reads.
Related
I use firebase authentication in my app and I have a widget called AuthGate that the app launches as its home. In AuthGate there is just a StreamBuilder that listens to FirebaseAuth.instance.authStateChanges() and returns different screens depending on the user state. When no user is logged in, the LoginScreen is returned, and when they log in through one of the methods, the StreamBuilder should send them to the HomeScreen. The same should happen when registering and when they sign out it should automatically send them back to the LoginScreen. My issue is that sometimes the changes are not detected at all, and even when they are, it takes at least a few seconds. Is there a way to make it update faster? I like this method a lot more than the way I used to do it (pushing replacements), because it's cleaner and more efficient, so I hope there is a solution. Here is the code in the AuthGate:
class AuthGate extends StatelessWidget {
const AuthGate({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const LoginScreen();
} else if (snapshot.data?.displayName == null ||
snapshot.data?.email == null ||
snapshot.data?.photoURL == null) {
return UpdateProfileScreen(user: snapshot.data!);
} else {
return HomeScreen(user: snapshot.data!);
}
});
}
}
Here i implement firebase login, register, home page setting in my flutter app.
For this i want to set Homepage according to firestore query in main.dart
Here is my code
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
//
return MaterialApp(
debugShowCheckedModeBanner:false,
home: setHomePage(),
);
}
}
setHomePage() {
if(FirebaseAuth.instance.currentUser==null){
return Login();
}
else {
FirebaseFirestore.instance.collection(AppString.FB_USERS).doc(
FirebaseAuth.instance.currentUser.uid).get().then((
DocumentSnapshot snapshot) {
if (snapshot.exists) {
return UserStateJunction();
}
else {
return User();
}
});
}
}
I think this code has no error ,
But this is not work and homepage is not waiting for firestore query , it throws null exception
Could not find a generator for route RouteSettings("/", null) in the
_WidgetAppState. Make sure your root app widget has provided a way to generate this route. Generators for routes are searched for in the following order:
For the "/" route, the "builder" property, if non-null, is used.
Otherwise, the "routes" table is used, if it has an entry for the route.
Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "builder" and "routes".
Finally, if all else fails onUnknownRoute is called. Unfortunately, onUnknownRoute was not set.
int main(){
runApp( YourApp() )
}
class YourApp extends StatelessWidget{
#override
Widget build(BuildContext context){
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot){
if (snapshot.hasData){
FirebaseUser user = snapshot.data; // this is your user instance
/// is because there is user already logged
return MainScreen();
}
/// other way there is no user logged.
return LoginScreen();
}
);
}
}
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)
In a flutter messaging app, chatrooms are created and on the conversation screen, I can access the subcollection of the messages. But when the same subcollection I am trying to access on the main page
(where existing chats are shown) I cannot access them.
I have a collection of ChatRooms, in which users as an array are stored. Then, Messages named subcollection stores the messages.
See, the document is named lexicographically attached with and in between. Further, it has Messages collection.
And messages collection is also not empty.
On the main page, the existing chats are shown in listTile. I want to show the last message in its subtitle.
So, here is the last message stateful widget.
class _LastMessageState extends State<LastMessage> {
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection("ChatRooms")
.doc(widget.chatRoomID)
.collection("Messages")
.orderBy("Time")
.snapshots()
.last,
builder: (context, documentSnapshot) {
return Text(documentSnapshot.data.docs.last.get("Message"));
});
}
}
Always the bad state error is coming up.
I would be glad if you could figure out the problem.
Edit :
This is my firestore rules.
You should use the limit() method, in order to get a QuerySnapshot with only one document. Then you can do as follows, knowing the first (and unique) element of the docs list is the document you are looking for:
return FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance.
.collection("ChatRooms")
.doc(widget.chatRoomID)
.collection("Messages")
.orderBy("Time", descending: true)
.limit(1)
.get(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text("...");
}
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.size > 0) {
return Text(snapshot.data.docs[0].get("Message"));
} else {
return Text("No document");
}
}
return Text("Loading");
},
);
I am trying to persist the firebase auth state in a flutter app by using this code from the documentation but when I kill the app in the emulator and open it again it doesn't recognize a user.
I can use sharedpreferences but I want to use only firebase, what am I doing wrong here?
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// Create the initialization Future outside of `build`:
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
final FirebaseAuth auth = FirebaseAuth.instance;
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _initialization,
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return (MaterialApp(
home: Warning(
warning: 'Error',
),
));
}
// once complete show your app
if (snapshot.connectionState == ConnectionState.done) {
print('CONNECTED');
if (AuthService().user() == null) {
return MaterialApp(
home: LoginPage(),
);
} else {
return MaterialApp(
home: HomePage(),
);
}
}
// if nothing happens return loading
return MaterialApp(
home: //LoginPage()
Warning(
warning: 'Loading',
),
);
},
);
}
}
AuthService class
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// auth change user stream
User user() {
// ignore: deprecated_member_use
_auth.authStateChanges().listen((User user) {
if (user == null) {
return null;
} else {
return user;
}
});
}
}
I hope you can help me to understand the problem and solve it, thank you.
Since authStateChanges returns a Stream, you'll want to use a StreamBuilder in your code to wrap that asynchronous operation too.
Something like this:
// once complete show your app
if (snapshot.connectionState == ConnectionState.done) {
print('CONNECTED');
return StreamBuilder(
stream: FirebaseAuth.instance. authStateChanges(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return MaterialApp(
home: LoginPage(),
);
} else {
return MaterialApp(
home: HomePage(),
);
}
}
)
}
Unrelated: you're repeated the code to create a MaterialApp quite frequently, which is not needed. For example, in the above snippet we could have only one mention of MaterialApp and get the same result with:
// once complete show your app
if (snapshot.connectionState == ConnectionState.done) {
print('CONNECTED');
return StreamBuilder(
stream: FirebaseAuth.instance. authStateChanges(),
builder: (BuildContext context, snapshot) {
return MaterialApp(
home: snapshot.hasData && snapshot.data != null ? HomePage() : LoginPage(),
)
}
)
}
If you do this for all mentions of MaterialApp and other duplication, you can reduce the code significantly, making it less error prone and easier to maintain.
It does persist. You are just not using the auth state listener correctly. The return statements from your authStateChanges listener are not actually escaping the call to user(). On top of that, the listener could return null the first time. It's not until some time later that the Firebase SDK determines that the user is actually valid and signed in. Your listener will get a second callback at that time. Your code need to be ready for this to happen - it can't just blindly take the first value, as the auth state might change over time.
I suggest adding some debug logging in your auth state listener to see how this actually works. Also I suggest reading this blog to understand how auth state listeners work in more detail.
You can use my code, You can use userChanges() instead of authStateChanges()
final Stream<User?> firebaseUserChanges = firebaseAuth.userChanges();