Flutter: Snapshot is not updating its data - flutter

So I'm trying to save data into firestore, but I have a class to signup which has this code:
FirebaseAuth auth = FirebaseAuth.instance;
await auth.createUserWithEmailAndPassword(
email: emailController.text, password: passwordController.text)
.then((value) => {
Navigator.pushNamed(context, 'DialogFlow'),
user=auth.currentUser,
user.sendEmailVerification(),
DatabaseService(uid:user.uid).UpdateUserData("", emailController.text, ChatScreenState().mess)
this will navigate me to the dialogflow, which has this code:
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
FirebaseAuth auth = FirebaseAuth.instance;
user=auth.currentUser;
DatabaseService db = DatabaseService(uid: user.uid);
return StreamBuilder(
stream: db.userData,
builder: (context , snapshot){
Userdata userdata=snapshot.data;
print("====================================");
print(snapshot.data);
print("====================================");
if (userdata != null) {
this.userTestMessage = "";
shhh = pressed ? true : false;
flag = true;
if (!Retrieved_messages) {
this.messsages = userdata.messsages; //Retrieve user data from firebase only once.
// Retrieve user data from firebase only once.
Retrieved_messages = true;
}//load only 20 messages at once . When we scroll up load more.
db.UpdateUserData(
firebaseUser.displayName, firebaseUser.email, this.messsages);
print(userdata.messsages);
print(mess);
print(userdata.messsages);
print("==============================");
print(snapshot.data);
print("==============================");
}
if (db.getUserMessages() == null) {
if (firebaseUser != null) {
db.UpdateUserData(
firebaseUser.displayName, firebaseUser.email, this.messsages);
}
}
and the database class is
DatabaseService({this.uid, this.messsages});
final CollectionReference userCollection = FirebaseFirestore.instance
.collection('users');
UpdateUserData(String Username, String Email,
List messsages) async
{ try {
return await FirebaseFirestore.instance.collection("users").doc(uid).set({
'Username': Username,
'Email': Email,
'messsages': messsages,
}
);
}catch(e){
print(e+" this is the error");
}
}
Future getUserMessages() async
{
FirebaseFirestore.instance.collection(uid).snapshots();
}
Userdata _userDataFromSnapshot(DocumentSnapshot snapshot) {
return Userdata(uid: uid,
name: snapshot.data()['Username'],
email: snapshot.data()['Email'],
messsages: snapshot.data()['messsages']
);
}
Stream<Userdata> get userData {
return userCollection.doc(uid).snapshots().map(_userDataFromSnapshot);
}
}
The problem I'm facing is that whenever I create a new user, and try to save new messages, the firestore keeps on saving and removing the messages, so its in an infinite loop, so I hope that someone can tell me how I can fix it that it saves the messages without removing them.
Note: the snapshot data isn't updating.

I believe the reason you have your build function rebuilt is usage of context.watch in the beginning of your build(). As in bloc documentation:
Using context.watch at the root of the build method will result in the entire widget being rebuilt when the bloc state changes. If the entire widget does not need to be rebuilt, either use BlocBuilder to wrap the parts that should rebuild, use a Builder with context.watch to scope the rebuilds, or decompose the widget into smaller widgets.

Related

Flutter - How can I get the Firestore items that contain their id in an array in another table as snapshots?

How can I get Firestore items containing their id in an array in another table as snapshots in Flutter? I am attaching the code that I have that works perfectly for me doing a "get", but I can't find a way to convert this to Stream and print it on the screen with the StreamBuilder instead of with the FutureBuilder and update it with each change
Future<List<DocumentSnapshot<Map<String, dynamic>>>?> getPools() async {
List<DocumentSnapshot<Map<String, dynamic>>> pools = [];
final user = FirebaseAuth.instance.currentUser;
final DbUser? dbUser = await dbUserAPI.getDbUser(user);
if (dbUser != null) {
for (var pool in dbUser.pools) {
final result = await FirebaseFirestore.instance
.collection('pools')
.doc(pool)
.get();
pools.add(result);
}
return pools;
}
if (kDebugMode) {
print('Error al leer el usuario de FireStore');
}
return null;
}
In the dbUsersAPI.getDbUsers function I retrieve the user data from the "Users" table and then I get the value ".pools", which is an array of Strings with the ids of the items I want to retrieve.
I have tried many ways and to play with Streams but I am always getting a Future or Stream when I only want to get a Stream of the items that I am filtering.
I have tried with the where clause but it does not update the values. I think the problem is that I don't know how to manage the Future returned by the dbUsersAPI.getDbUsers function.
Well for displaying data using a StreamBuilder you need to fetch data in streams by generating a requests that ends with .snapshots() method instead of a .get() method.
A pretty simple scenario will be,
DbUser? dbUser;
getDbUser() async {
final user = FirebaseAuth.instance.currentUser;
final DbUser? _dbUser = await dbUserAPI.getDbUser(user);
if(_dbUser != null){
dbUser = _dbUser;
}
}
#override
void initState(){
getDbUser();
super.initState();
}
#override
void build(BuildContext context){
return ListView.builder(
itemCount: dbUser!.pools.length,
itemBuilder: (context, index){
final pool = dbUser!.pools[index];
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('pools')
.doc(pool)
.snapshots(),
builder: (context, snapshots){
return Container();
}
}
);
);
}

What to do after login but before essential user data gets returned in flutter

I have a flutter app that uses firebase for authentication.
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return HomeScreen();
} else {
return LoginScreen();
}
},
);
so basically as soon as user authenticates, this will take to the home screen. but i dont want that, i want to wait on another piece of data from my api, say onboarded, if onboarded == true then HomeScreen otherwise OnboardingScreen.
So the tricky part is before that data comes in, i want to stay on the login screen. how do i have the user stay on the LoginScreen? it seems the best way is to have another stream listen to the onboardedLoading and combine these 2 streams?
Make a dart file auth.dart, in that, paste this line of code,
final FirebaseAuth auth = FirebaseAuth.instance;
Future<FirebaseUser> handleSignInEmail(String email, String password) async {
AuthResult result = await auth.signInWithEmailAndPassword(email: email, password: password);
final FirebaseUser user = result.user;
assert(user != null);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await auth.currentUser();
assert(user.uid == currentUser.uid);
print('signInEmail succeeded: $user');
return user;
}
Future<FirebaseUser> handleSignUp(email, password) async {
AuthResult result = await auth.createUserWithEmailAndPassword(email: email, password: password);
final FirebaseUser user = result.user;
assert (user != null);
assert (await user.getIdToken() != null);
return user;
}
In your login/ Sigup page, create an instance of my auth class:
var authHandler = new Auth();
In the onPressed () of your button
onPressed: () {
if(onboardedLoading==true){
authHandler.handleSignInEmail(emailController.text, passwordController.text)
.then((FirebaseUser user) {
Navigator.push(context, new MaterialPageRoute(builder: (context) => HomeScreen()));
}).catchError((e) => print(e));
}
}else{
//Show An Animation, such as CirclularProgressIndicator.
}
You can design a simple loading screen, then use Navigator.pushAndRemoveUntil() to whichever screen you need after getting AuthState.

How to get inside data in Future<Map<dynamic, dynamic>>?

Future<Map> returnUserMap() async {
final FirebaseUser currentUser = await _auth.currentUser();
Map userMap = {
"UserName": currentUser.displayName,
"UserEmail": currentUser.email,
"UserUrl": currentUser.photoUrl
};
print("1");
print(userMap);
return userMap;
}
return value type is Instance of 'Future>'.
I want to get a UserName, how can I do it?
Your function returnUserMap() returns a Future<Map>. I suspect that the error you describe is not in the code snippet you copied.
Whenever the task to be performed may take some time, you will receive a future. You can wait for futures in an async function with await.
It is therefore recommended to use a so-called FutureBuilder in your build() function:
FutureBuilder<FirebaseUser>(
future: _auth.currentUser(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
final FirebaseUser user = snapshot.data;
if (user.displayName == null || user.displayName.isEmpty())
return text(currentUser.email); // display email if name isn't set
return text(currentUser.displayName);
}
if (snapshot.hasError) {
return text(snapshot.error);
}
return text('loading...');
},
),
If you want to have the displayName outside your build() function, the following code should do the job when you are inside of an async function:
final FirebaseUser user = await _auth.currentUser();
final String displayName = user.displayName;
print('the displayName of the current user is: $displayName');
And this code when you are in a normal function:
_auth.currentUser().then((FirebaseUser user) {
String displayName = user.displayName;
print('displayName: $displayName');
}).catchError((error) {
print('error: ' + error.toString());
});
I think it's worth watching the following video for further understanding:
Async/Await - Flutter in Focus

Flutter Multiprovider with dependent streams

I'm want to make the User document from Firestore available to different widgets in my application. I'm listening to the authstate changes and getting the user ID, however, I'm not sure how to use that within a subsequent stream in my multiprovider :
return MultiProvider(
providers: [
Provider<FirebaseAuthService>(
create: (_) => FirebaseAuthService(),
),
//I'm looking to get another provider based on the provider above^^^ i.e the user id,
],
child: getScreen(),
);
class FirebaseAuthService {
final _firebaseAuth = FirebaseAuth.instance;
User _userFromFirebase(FirebaseUser user) {
return user == null ? null : User(uid: user.uid);
}
Stream<User> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
Future<User> signInAnonymously() async {
final authResult = await _firebaseAuth.signInAnonymously();
return _userFromFirebase(authResult.user);
}
Future<void> signOut() async {
return await _firebaseAuth.signOut();
}
}

Why I getting another connectionState type after connectionState.done in a StreamBuilder?

I have created a StreamController to handle authentication. I subscribe a user when the sign in is completed. So I create a class for that:
class AuthAPI {
final FacebookLogin facebookLogin = FacebookLogin();
final Dio _dio = Dio();
final StreamController<User> _authStatusController = StreamController<User>.broadcast();
Stream<User> get onAuthStatusChanged => _authStatusController.stream;
// Facebook Sign In
Future<User> facebookSignIn() async {
FacebookLoginResult result = await facebookLogin.logIn(['public_profile', 'email']);
switch(result.status) {
case FacebookLoginStatus.loggedIn:
return _sendFacebookUserDataToAPI(result);
case FacebookLoginStatus.error:
return null;
case FacebookLoginStatus.cancelledByUser:
print('Cancelled');
return null;
default:
return null;
}
}
// Sign Out
void signOut() async {
facebookLogin.logOut();
_authStatusController.sink.add(null);
_authStatusController.close();
}
Future<User> _sendFacebookUserDataToAPI(FacebookLoginResult result) async {
final String facebookToken = result.accessToken.token;
final Response graphResponse = await _dio.get(
'https://graph.facebook.com/v4.0/me?fields='
'first_name,last_name,email,picture.height(200)&access_token=$facebookToken');
final profile = jsonDecode(graphResponse.data);
ApiProvider apiProvider = ApiProvider();
UserSocialAuth userSocialAuth = UserSocialAuth(
firstName: profile['first_name'],
lastName: profile['last_name'],
email: profile['email'],
provider: 'facebook',
providerUserId: profile['id']
);
Map socialSignIn = await apiProvider.socialSignIn(userSocialAuth);
User user;
if (socialSignIn.containsKey('access_token')) {
Map userData = await apiProvider.currentUser(socialSignIn['access_token']);
user = User.fromJson(userData['data']);
apiProvider.setAccessToken(socialSignIn['access_token']);
_authStatusController.sink.add(user);
print("Login Successful");
} else {
_authStatusController.sink.addError(socialSignIn['error']);
}
_authStatusController.close();
return user;
}
}
and this is my StreamBuilder:
return StreamBuilder(
stream: userBloc.authStatus,
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot.connectionState);
switch(snapshot.connectionState) {
case ConnectionState.active:
User user = snapshot.data;
if (user == null) {
return SignInSignUpScreen();
}
return _showHomeUI(user, snapshot);
case ConnectionState.done:
User user = snapshot.data;
if (user == null) {
return SignInSignUpScreen();
}
print(user);
return _showHomeUI(user, snapshot);
default:
return Center(child: CircularProgressIndicator());
}
}
);
So, when I make the login, then it shows a CircularProgressIndicator, and if the authentication is successful, then it has to show the home screen. But, it stills showing the login screen, and when I print the output of the connectionState, I see that after the connectionState.done, the connectionState pass to connectionState.waiting and I do not know why.
Here is the output of the console:
And when it reaches to the last connectionState.done, it does not have data.
You're calling _authStatusController.close(); in the end of _sendFacebookUserDataToAPI method – that means that the underlying stream is finished and you stream listener enters "done" state.
You should instead create e.g. dispose() method in AuthAPI class and call _authStatusController.close() there. This method should be called when AuthAPI is no longer needed.