Wait for a future function to complete its execution - firebase

I am new to flutter and in my below code there is read data function is called at line 18(it performs Firebase Database operations) I want that the readData() function must complete its execution before going to the print statement(line 19) and further execution.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/cupertino.dart';
import 'package:udharibook/Screens/SignInPage.dart';
import 'package:udharibook/Screens/UserProfile.dart';
import 'package:udharibook/Screens/dashboard.dart';
class AuthService {
bool flag = false;
final FirebaseAuth _auth = FirebaseAuth.instance;
final DBRef = FirebaseDatabase.instance.reference().child('Users');
handleAuth(){
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext, snapshot) {
if(snapshot.hasData) {
readData();
print(flag);
if(flag ==true)
return DashboardPage();
else
return UserProfile();
}
else {
return SignIn();
}
},
);
}
Future<void> readData() async {
final FirebaseUser user = await _auth.currentUser();
final userid = user.uid;
DBRef.child(userid).once().then((DataSnapshot data){
print(userid);
if(data.value!=null)
{
flag = true;
print(data.key);
print(data.value);
}
else{
print('User not found');
flag = false;
}
});
}
signOut(){
FirebaseAuth.instance.signOut();
}
signIn(AuthCredential authCreds){
FirebaseAuth.instance.signInWithCredential(authCreds);
}
signInWithOTP(smsCode,verId){
AuthCredential authCreds = PhoneAuthProvider.getCredential(
verificationId: verId,
smsCode: smsCode
);
signIn(authCreds);
}
}

If you want a future to complete inside a build/builder method then using FutureBuilder is a good way to go. This way you can set up a placeholder widget whilst the future is completing, and then once its done output the desired results. In the example below I've used a circular progress indicator as the placeholder but you could use any widget:
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext, snapshot) {
if(snapshot.hasData) {
return FutureBuilder(
future: readData(),
builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) return CircularProgressIndicator();
print(flag);
if(flag ==true)
return DashboardPage();
else
return UserProfile();
}
);
}
else {
return SignIn();
}
},
);

Since readData() is An AsyncFunction ,
Adding the keyword await before readData() , will force readData() to finish,and after that it will print , However you can't do it since StreamBuilder is waiting for Widget return type.
So what you can do is .
maybe modify your readData to return a Future boolean readData() ;
Future<bool> readData() async {
final FirebaseUser user = await _auth.currentUser();
final userid = user.uid;
DBRef.child(userid).once().then((DataSnapshot data){
print(userid);
if(data.value!=null)
{
print(data.key);
print(data.value);
return true;
}
else{
print('User not found');
return false;
}
});
}
and you call it :
readData().then((value)
{
if (value ==true)
return Widget
else
return Widget;
}
);
Hope that helps.

Related

How to fix circular wait progress while logging out in flutter app using Firebase authentication?

I have a LoginPage() for login and a DashBoard() page which comes after logging in succesfully.
I am using a Controller() page to provide the authetication which listens for any authentication changes in firebase, it looks like this :
class Controller extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
else if (snapshot.hasData) {
return DashBoard();
}
return LoginPage();
},
));
}
}
I also have a Log out button in another page. When I try to logout, it throws me first to DashBoard() page (which means that snapshot.hasData has some value) and after 2-3 seconds it throws me then to LoginPage() but the code never goes into this section
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
It first goes into this block conditon
else if (snapshot.hasData) {
return DashBoard();
}
And then the LoginPage() section. Also when I try to Login, it never shows me the Circularwait, but throws me to DashBoard() page.
What is the best way to achieve this ?
My fireBase auth file looks like this :
class GoogleSignInProvider extends ChangeNotifier {
final googleSignIn = GoogleSignIn();
GoogleSignInAccount _user;
GoogleSignInAccount get user => _user;
Future signInWithGoogle() async {
try {
final GoogleSignInAccount googleuser = await googleSignIn.signIn();
if (googleuser == null) return;
_user = googleuser;
final GoogleSignInAuthentication googleAuth =
await googleuser.authentication;
final GoogleAuthCredential credential = GoogleAuthProvider.credential(
idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
// Fluttertoast.showToast(msg: "Account created");
await FirebaseAuth.instance.signInWithCredential(credential);
print(_user);
notifyListeners();
} catch (e) {
print(e.toString());
}
}
Future signOutGoogle() async {
await googleSignIn.disconnect();
FirebaseAuth.instance.signOut();
}
}
interact with the bloc using a streambuilder in your ui. the ui creates events which the bloc code handles and response with state output
abstract class LoginEvent extends Equatable{
const LoginEvent();
#override
List<Object>get props=>[];
}
class LoginUser{
final String email;
final String password;
const LoginUser(this.email,this.password);
String get getEmail { return this.email;}
String get getPassword{ return this.password;}
}
class AuthenticateEvent extends LoginEvent{
final LoginUser user;
const AuthenticateEvent(this.user);
#override
List<Object> get props => [user];
LoginUser get getUser{return this.user;}
}
class LoginState extends Equatable{
final LoginView _login;
const LoginState(this._login);
#override
List<Object> get props => [_login];
LoginView get getLogin {return this._login;}
}
class BlocLogin
{
Stream<LoginState> get loginStream => _loginController.stream;
final _loginController = BehaviorSubject<LoginState>();
void dispose()
{
_loginController.close();
}
authenticate(BuildContext context,LoginEvent loginEvent) async
{
if (loginEvent is AuthenticateEvent)
{
LoginView param =
new LoginView(loginEvent.getUser.getEmail, loginEvent.getUser.getPassword);
LoginView loginValue =await Provider.of<Api>(context, listen: false)
.addLogin(context, param);
if (loginValue.returnMessage == "Failed") {
DialogCaller.showIncorrectLoginDialog(context).then((value2) {});
} else {
Provider.of<Api>(context, listen: false).dataCache.login = loginValue;
LoginState loginState=new LoginState(loginValue);
_loginController.sink.add(loginState);
}
}
}
}

Flutter : Save user id from firestore using Shared Preferences and retrieve the value into class

So first of all I'm new in Flutter. I want to use current sign in user id from firebase as filter from data that i want to show in apps.Please Help Me.
here is the user id from wrapper.dart I want to use.
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final User user = Provider.of<User>(context);
print(user.uid);
and here i want to pass the value as uid from database.dart
Stream<List<InitialRPP>> get dataInitRPP {
return dbRPP
.where('uid', isEqualTo: uid)
.snapshots()
.map(_initRPPFromSnapshot);
}
Here is the full source code
wrapper.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rppapps/models/user.dart';
import 'package:rppapps/screens/authenticate/authenticate.dart';
import 'package:rppapps/screens/home/home.dart';
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final User user = Provider.of<User>(context);
print(user.uid);
// Login or Home
if (user == null) {
return Authenticate();
} else {
return Home();
}
}
}
database.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:rppapps/models/model.dart';
class DatabaseService {
// the code should be in here "String uid;" like that
//collection reference
final CollectionReference dbRPP = Firestore.instance.collection('rpp');
Future addinitialRPP(String uid, String nama, String tahun, String kelas,
String semester, String mapel, String materi) async {
return await dbRPP.add({
'uid': uid,
'nama': nama,
'tahun': tahun,
'kelas': kelas,
'semester': semester,
'mapel': mapel,
'materi': materi
});
}
List<InitialRPP> _initRPPFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return InitialRPP(
uid: doc.data['uid'] ?? '',
nama: doc.data['nama'] ?? '',
tahun: doc.data['tahun'] ?? '',
kelas: doc.data['kelas'] ?? '',
semester: doc.data['semester'] ?? '',
mapel: doc.data['mapel'] ?? '',
materi: doc.data['materi'] ?? '');
}).toList();
}
Stream<List<InitialRPP>> get dataInitRPP {
return dbRPP
.where('uid', isEqualTo: uid)
.snapshots()
.map(_initRPPFromSnapshot);
}
}
EDIT: (sign in sign out method and firebase_auth)
auth.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:rppapps/models/user.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// created user object based on FirebaseUser
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
// auth change user stream
Stream<User> get user {
return _auth.onAuthStateChanged.map(_userFromFirebaseUser);
}
// sign anon
Future signInAnon() async {
try {
AuthResult result = await _auth.signInAnonymously();
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign with email and pass
Future signInWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// register with email and pass
Future registerWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch (e) {
print(e.toString());
return null;
}
}
}
pubspec.yaml
firebase_auth: ^0.14.0+5
cloud_firestore: ^0.12.9+4
provider: ^3.1.0
Try using onAuthStateChanged() instead. You can check if the user is logged in by adding this Stream to a Streambuilder. Any time the user logs out or in, the Stream automatically updates. Then wrap your Home widgets with a FutureBuilder and pass the currentUser() future. This will return a snapshot containing the user information, such as email and uid.
Finally, you can filter widgets by checking if the uid is the same as the given one. For example, if a user is admin or not.
wrapper.dart
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<FirebaseUser>(
stream: AuthService().authStateChanges(),
builder: (context, AsyncSnapshot snapshot) {
// if the stream has data, the user is logged in
if (snapshot.hasData) {
// isLoggedIn
return Home();
} else if (snapshot.hasData == false &&
snapshot.connectionState == ConnectionState.active) {
// isLoggedOut
return Authenticate();
} else {
return CircularProgressIndicator();
}
},
),
);
}
}
auth.dart
class AuthService {
final FirebaseAuth _firebaseInstance = FirebaseAuth.instance;
final CollectionReference _usersCollection =
Firestore.instance.collection("users");
// User State
Stream<FirebaseUser> authStateChanges() {
FirebaseAuth _firebaseInstance = FirebaseAuth.instance;
return _firebaseInstance.onAuthStateChanged;
}
// Current User
Future<FirebaseUser> currentUser() async {
FirebaseAuth _firebaseInstance = FirebaseAuth.instance;
return _firebaseInstance.currentUser();
}
// Sign Out
Future<void> signOut() async {
FirebaseAuth _firebaseInstance = FirebaseAuth.instance;
return _firebaseInstance.signOut();
}
// Sign In Anonymously
Future<AuthResult> signInAnon() async {
return await _firebaseInstance.signInAnonymously().catchError((error) {
print(error);
});
}
// Sign In With Email And Password
Future<AuthResult> signIn(String email, String password) async {
return await _firebaseInstance
.signInWithEmailAndPassword(email: email, password: password)
.catchError((error) {
switch (error.code) {
case "ERROR_INVALID_EMAIL":
print("ERROR_INVALID_EMAIL");
break;
case "ERROR_WRONG_PASSWORD":
print("ERROR_WRONG_PASSWORD");
break;
case "ERROR_USER_NOT_FOUND":
print("ERROR_USER_NOT_FOUND");
break;
case "ERROR_USER_DISABLED":
print("ERROR_USER_DISABLED");
break;
case "ERROR_TOO_MANY_REQUESTS":
print("ERROR_TOO_MANY_REQUESTS");
break;
case "ERROR_NETWORK_REQUEST_FAILED":
print("ERROR_NETWORK_REQUEST_FAILED");
break;
}
});
}
// Create User With Email And Password
Future<AuthResult> signUp(String email, String password) async {
return await _firebaseInstance
.createUserWithEmailAndPassword(email: email, password: password)
.catchError(
(error) {
switch (error.code) {
case "ERROR_INVALID_EMAIL":
print("ERROR_INVALID_EMAIL");
break;
case "ERROR_WEAK_PASSWORD":
print("ERROR_WEAK_PASSWORD");
break;
case "ERROR_EMAIL_ALREADY_IN_USE":
print("ERROR_EMAIL_ALREADY_IN_USE");
break;
case "ERROR_NETWORK_REQUEST_FAILED":
print("ERROR_NETWORK_REQUEST_FAILED");
break;
}
},
).then((user) {
if (user != null) {
_usersCollection.document(user.user.uid).setData(
{
"email": user.user.email,
"uid": user.user.uid,
},
);
return null;
} else {
return null;
}
});
}
}
authenticate.dart
class Authenticate extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(child: Text("Authenticate")),
// Sign In Button
RaisedButton(
onPressed: () => AuthService().signIn("testemail01#gmail.com", "password"),
child: Text("Sign In as user 01"),
),
RaisedButton(
onPressed: () => AuthService().signIn("testemail02#gmail.com", "password"),
child: Text("Sign In as user 02"),
)
],
);
}
}
home.dart
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<FirebaseUser>(
future: AuthService().currentUser(),
builder: (context, snapshot) {
if (snapshot.hasData) {
String userEmail = snapshot.data.email;
String userUid = snapshot.data.uid;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(child: Text("Home")),
// Get Current User Email
Center(child: Text(userEmail)),
// Get Current User UID
Center(child: Text(userUid)),
// Filter By UID
Builder(
builder: (context) {
if (userUid == "X6Ibch8OwmZWrYIB1F3IPpbBQbk2") {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.admin_panel_settings),
Text("Admin"),
],
);
}
return Container();
},
),
// Sign Out Button
RaisedButton(
onPressed: () => AuthService().signOut(),
child: Text("Sign Out"),
)
],
);
} else {
return CircularProgressIndicator();
}
},
);
}
}

Firebase email verification Flutter

I know this question has been asked a lot and I have spent a lot of time reading and trying to implement the answers. So I am trying to get the response from isEmailVerified from Firebase Auth to work and it does work but right now it always returns false unless I refresh the app or close it and reopen it. which is obviously a bad user experience. How do I get the response to update without having to close the app.
here is the relevant pieces of code.
Future<bool> isEmailVerified() async {
FirebaseUser user = await _auth.currentUser();
if (user == null) {
return false;
} else {
await user.reload();
user = await _auth.currentUser();
return user.isEmailVerified;
}
}
main.dart
child: Consumer<Auth>(
builder: (_, auth, __) => MaterialApp(
theme: Provider.of<ThemeNotifier>(context).getTheme(),
home: FutureBuilder(
future: Future.wait([auth.isEmailVerified(), auth.tryAutoLogin()]),
builder: (BuildContext ctx, AsyncSnapshot authResultSnapshot) =>
authResultSnapshot.connectionState == ConnectionState.done
? authResultSnapshot.data[1]
? authResultSnapshot.data[0]
? HearingsScreen()
: SplashScreen(
emailVerified: true,
)
: LoginScreen()
: SplashScreen(),
),
It is not returning true until I restart the app
Things I have tried besides this:
1) await user.getIdToken(refresh: true);
2) sign user out then back in
3) firebase_user_stream package
Any help is appreciated.
I have implemented the same scenario in a splash screen with below code, you can change it as per your requirement. :
//To check is User is logged in
Future<bool> isLoggedIn() async {
FirebaseUser user = await _fireBaseAuth.currentUser();
if (user == null) {
return false;
}
return user.isEmailVerified;
}
and
countDownTime() async {
return Timer(
Duration(seconds: splashDuration),
() async {
if (await userAuth.isLoggedIn()) {
Navigator.pushReplacement(
context,
ScaleRoute(
widget: HomeScreen(),),
);
}
} else {
Navigator.pushReplacement(
context,
ScaleRoute(
widget: LoginScreen(),),
);
}
},
);
}
and
#override
void initState() {
super.initState();
countDownTime();
}
Update
One needs to implement isEmailVerified in initState() function periodically which can be the ideal approach to execute the verification with firebase.
bool _isUserEmailVerified;
Timer _timer;
#override
void initState() {
super.initState();
// ... any code here ...
Future(() async {
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
await FirebaseAuth.instance.currentUser()..reload();
var user = await FirebaseAuth.instance.currentUser();
if (user.isEmailVerified) {
setState((){
_isUserEmailVerified = user.isEmailVerified;
});
timer.cancel();
}
});
});
}
#override
void dispose() {
super.dispose();
if (_timer != null) {
_timer.cancel();
}
}

Async await flutter firestore

I would like to ask whats going on with my code.
Assuming the 'Counter' field is 179 in this instance, how do I make my outside myData update before printing?
class Test {
Firestore _firestore = Firestore.instance;
var myData;
void getData() async {
DocumentSnapshot snapshot =
await _firestore.collection('Counter').document('Counter').get();
myData = await snapshot.data['Counter'];
print('inside $myData');
}
void checkMyData() {
myData = 5;
getData();
print('outside $myData');
}
}
Console:
flutter: outside 5
flutter: inside 179
You have to make getData() return a Future like this:
Future getData() async {
So you can do this:
getData().then((value) {
print('value: $value');
}).catchError((error) {
print('error: $error');
});
But you probably want to use a FutureBuilder to show the information when arrives, like this:
FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('value: ${snapshot.data}');
} else if (snapshot.hasError){
return Text('error: ${snapshot.error}');
}
return Text('loading...');
},
)

Flutter StreamBuilder returns null from Firestore

The idea is to display a string from a random document within a collection in Firebase. A simple function getRandom() retrieves the total number of documents and generates a random integer r that is fed into the Firebase instance.
The output in the app is always null.
StreamBuilder(
initialData: Words(),
stream: getWords(),
builder: (context, snapshot){
if(!snapshot.hasData){
return Center(child: Text("NO DATA"));
}else {
var r = snapshot.data;
return Center(child: Text("${r.english}"));
}
})
Stream<Words> getWords() async* {
int r = await getRandom();
print("RANDOM NO: " + "$r");
Firestore.instance.document("vocabs/foods/words/$r")
.get()
.then((snapshot){
try {
return Words().english;
} catch(e){
print("ERROR");
return null;
}
});
}
class Words{
Words(): super();
String english;
Words.fromSnapshot(DocumentSnapshot snapshot)
: english = snapshot.data["english"];
}
I've constructed a this piece of sample code for you to give you some options to achieve what you'd like to do:
import 'dart:async';
class Word {
final String english;
const Word(this.english);
}
Future<Iterable<Word>> get firebaseSnapshot async => [ Word('aWord'), Word('bWord'), Word('cWord') ];
Stream<String> getEnglishWords() async* {
yield* await firebaseSnapshot.then((words) => Stream.fromIterable(words.map((w) => w.english)));
}
Stream<String> getEnglishWords2() async* {
final words = await firebaseSnapshot.then((words) => words.map((w) => w.english));
yield* Stream.fromIterable(words);
}
Stream<String> getEnglishWords3() async* {
final snapshot = await firebaseSnapshot;
for(final word in snapshot) {
yield word.english;
}
}
main() async {
await for(final englishWord in getEnglishWords()) {
print(englishWord);
}
await for(final englishWord in getEnglishWords2()) {
print(englishWord);
}
await for(final englishWord in getEnglishWords3()) {
print(englishWord);
}
}
Option No. 2 is the one I'd use. There is some significant performance consideration around it. I am scraping the back of my mind for the lecture around it... Nope, can't recall... If I find it, I'll update ya.

Resources