Asynchronous Function Error In Dart-Flutter - firebase

So I'm trying to create a simple Sign In page in Flutter using Google Firebase but Firebase is not giving me desired output. Maybe its error in Asynchronous Function cause its giving me output Instance of 'Future<dynamic>'
Code for Anonymously Sign In dart file:
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future signAnonymous() async {
try {
AuthResult result = await _auth.signInAnonymously();
FirebaseUser user = result.user;
return user;
} catch (e) {
print(e.toString());
return null;
}
}
}
Sign In Page Code:
import 'package:flutter/material.dart';
import 'package:freezeria/Screens/services/auth.dart';
class signin extends StatefulWidget {
#override
_signinState createState() => _signinState();
}
class _signinState extends State<signin> {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.purple[300],
appBar: AppBar(
backgroundColor: Colors.purple[600],
elevation: 0.0,
centerTitle: true,
title: Text('Sign In To Freezeria!'),
),
body: Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 50.0),
child: ElevatedButton(
child: Text('SignIn'),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.purple[100]),
),
onPressed: () async {
dynamic result = _auth.signAnonymous();
if (result == null) {
print("Error Signing In");
} else {
print('Signed In');
print(result);
}
}
)
),
);
}
}
The Output I got was:
I/flutter ( 8533): Signed In
I/flutter ( 8533): Instance of 'Future<dynamic>'
I need to get user details provided by the firebase
Any help will be appreciated :)

_auth.signAnonymous() is an async function, for that matter it returns a Future ! Use the await keyword to get the user value instead of a Future object !
result = await _auth.signAnonymous();

Related

_CastError (Null check operator used on a null value) because it requires loading

So I am creating an Instagram clone and when I open the feed screen, an error that says '_CastError (Null check operator used on a null value)' pops up.
But when I left it for a minute it was replaced by the feed screen normally
So it turns out it wants to load, but I am not sure how can I make it do so.
Here is the code(The bolded line is the one that requires time):
`
class PostCard extends StatefulWidget {`
`final snap;`
`const PostCard({Key? key, required this.snap}) : super(key: key);`
`#override`
`State<PostCard> createState() => _PostCardState();`
`}`
`class _PostCardState extends State<PostCard> {`
`bool isLikeAnimating = false;`
`int commentLen = 0;`
`bool isLoading = false;`
`#override`
`Widget build(BuildContext context) {`
`
** final UserProvider userProvider = Provider.of<UserProvider>(context);**`
`return isLoading`
`? const Center(`
`child: CircularProgressIndicator(),`
`)`
`: Container(`
`color: Colors.white,`
`padding: EdgeInsets.symmetric(vertical: 10),`
`child: Column(`
`children: [`
`Container( //inside here are the contents of the post`
`);`
`}`
`}`
`Feed Screen:`
`StreamBuilder(`
`stream: stream,`
`builder: (context,`
`AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {`
`if (snapshot.connectionState == ConnectionState.waiting) {`
`return Center(`
`child: CircularProgressIndicator(),`
`);`
`}`
`return ListView.builder(`
`itemCount: snapshot.data!.docs.length,`
`itemBuilder: (context, index) => PostCard(`
`snap: snapshot.data!.docs[index],`
`),`
`);`
`}));`
I am following the same course. I will search the solution in the Discord server and I found it:
Change the code in user_provider.dart like this:
class UserProvider with ChangeNotifier {
User? _user;
final AuthMethods _authMethods = AuthMethods();
User? get getUser => _user;
Future<void> refreshUser() async {
User user = await _authMethods.getUserDetails();
_user = user;
notifyListeners();
}
}
And this is the code for add_post_screen.dart:
#override
Widget build(BuildContext context) {
final User? user = Provider.of<UserProvider>(context).getUser;
return user == null
? const Center(
child: CircularProgressIndicator(
color: Colors.black,
))
: _file == null
? Center(
child: IconButton(
icon: const Icon(Icons.upload),
onPressed: () => _selectImage(context),
),
)
: Scaffold(
appBar: AppBar(
backgroundColor: Colors.grey,
leading: IconButton(
icon: const Icon(Icons.arrow_back), onPressed: () {}),
Following that you have to change your user_provider.dart as follows:
class UserProvider with ChangeNotifier {
User? _user;
final AuthMethods _authMethods = AuthMethods();
// Change it
User? get getUser => _user;
// update the value of an user
Future<void> refreshUser() async {
User user = await _authMethods.getUserDetails();
_user = user;
notifyListeners();
}
}
And in your post_card.dart you have to add a verification when the user == null, if it's null you have to put a CircularProgressIndicator, else the next of your code that show all the posts:
#override
Widget build(BuildContext context) {
final model.User? user = Provider.of<UserProvider>(context).getUser;
/*Fixing error related with null value located in user provider*/
return user == null
? const Center(
child: CircularProgressIndicator(
color: Colors.white,
),
)
: Container(//the rest of your code)

Flutter code loops infinitely even after data is loaded from firebase

I am using the following Flutter and Firebase code to scaffold out a page to a user
import 'package:fgd6ss/models/user.dart';
import 'package:fgd6ss/screens/user/usr_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:convert';
class UsertLanding extends StatefulWidget {
final Map code;
UserLanding({this.code});
User _user = User();
bool dataLoaded = false;
#override
_UserLandingState createState() => _UserLandingState();
}
class _UserLandingState extends State<UserLanding> {
#override
Widget build(BuildContext context) {
bool isValidUser = false;
dynamic userData;
Map codeData = widget.code;
try{
var document = FirebaseFirestore.instance.collection('users').where('id',isEqualTo: codeData['id']);
document.get().then((QuerySnapshot snapshot){
if (snapshot.docs.isNotEmpty) {
if (this.mounted) {
setState(() {
userData = snapshot.docs;
widget._user.name = userData[0]['name'];
widget._user.status = userData[0]['status'];
widget._user.type = userData[0]['type'];
print(widget._user.name);
});
}
}
});
}catch(e) {
print('error firebase data fetch');
}
return Scaffold(
backgroundColor: Color(0xfffefefe),
body: SafeArea(
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
widget._user.name,
style: TextStyle(
fontSize: 22.0
),
),
)
]
)
)
);
}
}
As you can see from the above code, I have a print statement inside the query in try. When I run this code, I expect it to run once when the screen loads. But what happens is, the code keeps looping and prints out the users name again and again on the console. Is this expected? If not, what is causing this behaviour? If yes, will it cause increase in the document read quota count on Firebase end.
You have to create a separate method and call this method into your initState(). Build function is run continuously so your print statement is printed in the loop. So try with the below code. initState() method run only once when you reach on to the page
class UsertLanding extends StatefulWidget {
final Map code;
UserLanding({this.code});
User _user = User();
bool dataLoaded = false;
#override
_UserLandingState createState() => _UserLandingState();
}
class _UserLandingState extends State<UserLanding> {
bool isValidUser = false;
dynamic userData;
Map codeData;
#override
initState() {
super.initState();
codeData = widget.code;
getData();
}
getData() {
try {
var document = FirebaseFirestore.instance
.collection('users')
.where('id', isEqualTo: codeData['id']);
document.get().then((QuerySnapshot snapshot) {
if (snapshot.docs.isNotEmpty) {
if (this.mounted) {
setState(() {
userData = snapshot.docs;
widget._user.name = userData[0]['name'];
widget._user.status = userData[0]['status'];
widget._user.type = userData[0]['type'];
print(widget._user.name);
});
}
}
});
} catch (e) {
print('error firebase data fetch');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfffefefe),
body: SafeArea(
child: Row(children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
widget._user.name,
style: TextStyle(fontSize: 22.0),
),
)
])));
}
}
You are calling your function in the build method. So whenever the build method will be rebuilt or refreshed, it will call that function again and again. The better approach is to call it in your InitState so it will be called only once. Here is an example that might help you.
#override
initState() {
getData();
super.initState();
}

Can't update Flutter Firebase_Auth from version 0.15 to 0.18 while keeping the StreamProvider

I have a large Flutter app that is working perfectly...
Now, I am struggling how to update the Firebase firebase_auth: ^0.15.5+3 plugin that has Breaking changes (since summer 2020).
I absolutely need to keep my StreamProvider as I check MyUser in multiple locations of the app.
I re-created a minimal working app to focus on my specific problem.
Can you suggest how I should adjust these:
1- getCurrentUser in AuthService: It seems I need to use authStateChangesbut dont know how.
2- StreamProvider in MyApp: How to adjust this so I can keep using it in my app without changing anything.
3- Using this way: final currentUser = Provider.of<MyUser>(context) then currentUser.uidto get the uid String.
Please, be specific with concrete example.
Here is my puspec.yaml
name: myApp
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# **** FIREBASE ****
firebase_auth: ^0.15.5+3
provider: ^4.0.5
#firebase_core: "^0.5.2"
#firebase_auth: "^0.18.3"
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/
here is all the (working) code with previous Firebase version 0.15
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:firebase_auth/firebase_auth.dart';
void main() {
runApp(MyApp());
}
//******************************************
class MyApp extends StatelessWidget {
#override Widget build(BuildContext context) {
//The following StreamProvider is what I am trying to recreate with new Firebase version.
return StreamProvider<MyUser>.value(
value: AuthService().getCurrentUser,
child: MaterialApp( title: 'MyApp',
home: Wrapper(),),);}
}
//******************************************
class Wrapper extends StatelessWidget {
#override Widget build(BuildContext context) {
final currentUser = Provider.of<MyUser>(context);
// return either the Home or Authenticate widget
if (currentUser == null) {return SignIn();}
else {return HomePage();}}
}
//******************************************
class MyUser {
String uid;
MyUser({this.uid ,});
}
//******************************************
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
//This method will need to be updated I guess
Stream<MyUser> get getCurrentUser {
return _auth.onAuthStateChanged.map((FirebaseUser user) => _refereeFromFirebaseUser(user));
}
MyUser _refereeFromFirebaseUser(FirebaseUser _authUser) {
return (_authUser != null) ? MyUser(uid: _authUser.uid) : null;
}
Future signInWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
return user;
} catch (error) {
print(error.toString());
return null;}
}
Future signOut() async {
try {return await _auth.signOut();}
catch (error) {print(error.toString());return null;}
}
}
//******************************************
class HomePage extends StatelessWidget {
final AuthService _auth = AuthService();
#override Widget build(BuildContext context) {
final currentUser = Provider.of<MyUser>(context);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('MyApp2'),
actions: <Widget>[FlatButton.icon(icon: Icon(Icons.person), label: Text('logout'), onPressed: () async {await _auth.signOut();},), ],),
//Following line is very important: this is what I'd like to replicate with new Firebase version
body: Text("My ID is ${currentUser.uid}"),
),);
}
}
//******************************************
class SignIn extends StatefulWidget {
#override _SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
final AuthService _auth = AuthService();
final _formKey = GlobalKey<FormState>();
String error = '';
String email ;
String password ;
#override Widget build(BuildContext context) {
return Scaffold(
body: Form(key: _formKey,
child: ListView(shrinkWrap: true, children: <Widget>[ SizedBox(height: 60.0),
emailField(), SizedBox(height: 8.0),
passwordField(), SizedBox(height: 24.0),
signInButton(),
Text(error, style: TextStyle(color: Colors.red, fontSize: 16.0),),
],),),);}
RaisedButton signInButton() {
return RaisedButton( child: Text('Sign In'),
onPressed: () async {
if(_formKey.currentState.validate()) {
dynamic result = await _auth.signInWithEmailAndPassword(email, password);
//If successful SignIn, the Provider listening in the Wrapper will automatically load the Home page.
if(result == null) {setState(() {error = 'Could not sign in with those credentials';});}}});
}
TextFormField passwordField() {
return TextFormField(
validator: (val) => val.length < 6 ? 'Enter a password 6+ chars long' : null,
onChanged: (val) {setState(() => password = val);},);
}
TextFormField emailField() {
return TextFormField(
validator: (val) => val.isEmpty ? 'Enter an email' : null,
onChanged: (val) {setState(() => email = val);},);
}
}
First point: the current user is not async anymore. If you check the documentation you will see that it doesn't return a future but a User. If you want to check for auth state updates just call FirebaseAuth.instance.authStateChanges(). Another thing to notice is that now the class containing users' data is not called anymore FirebaseUser but just User.
StreamProvider is still available in the provider package. In your question it isn't clear what is the issue that this class is raising to you.
Provider.of<>(context) can still be used. However now you can also use these 2 alternatives methods:
context.watch(), which makes the widget listen to changes on T
context.read(), which returns T without listening to it
I was able to migrate the whole code. It is now 100% running with new firebase_auth: "^0.18.3".
(Big thanks to Net Ninja for his web course with the initial code)
Here is new pubspec.yaml:
name: flutter_app
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# **** FIREBASE ****
provider: ^4.0.5
firebase_core: "^0.5.2"
firebase_auth: "^0.18.3"
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/
and here is the main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart'; //New
void main() {
WidgetsFlutterBinding.ensureInitialized();//New
runApp(Initialize());
}
//******************************************
class Initialize extends StatelessWidget {//New
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override Widget build(BuildContext context) {
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
if (snapshot.hasError) {print("Error-01"); return myCircularProgress();} //to be updated
if (snapshot.connectionState == ConnectionState.done) {return MyApp();}
else {print("loading-02"); return myCircularProgress();}
},);}
Center myCircularProgress() => Center(child: SizedBox(child: CircularProgressIndicator(), height: 100.0, width: 100.0,));
}
//******************************************
class MyApp extends StatelessWidget {
#override Widget build(BuildContext context) {
return StreamProvider<MyUser>.value(
value: AuthService().getCurrentUser,
child: MaterialApp( title: 'MyApp',
home: Wrapper(),),);}
}
//******************************************
class Wrapper extends StatelessWidget {
#override Widget build(BuildContext context) {
final currentUser = Provider.of<MyUser>(context);
// return either the Home or Authenticate widget
if (currentUser == null) {return SignIn();}
else {return HomePage();}}
}
//******************************************
class MyUser {
String uid;
MyUser({this.uid ,});
}
//******************************************
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Stream<MyUser> get getCurrentUser {
return _auth.authStateChanges().map((User user) => _refereeFromFirebaseUser(user)); // Also works with "_auth.idTokenChanges()" or "_auth.userChanges()"
}
MyUser _refereeFromFirebaseUser(User _authUser) {
return (_authUser != null) ? MyUser(uid: _authUser.uid) : null;
}
Future signInWithEmailAndPassword(String email, String password) async {
try {
//AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User user = result.user;
return user;
} catch (error) {
print(error.toString());
return null;}
}
Future signOut() async {
try {return await _auth.signOut();}
catch (error) {print(error.toString());return null;}
}
}
//******************************************
class HomePage extends StatelessWidget {
final AuthService _auth = AuthService();
#override Widget build(BuildContext context) {
final currentUser = Provider.of<MyUser>(context);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('MyApp v0.18'),
actions: <Widget>[FlatButton.icon(icon: Icon(Icons.person), label: Text('logout'), onPressed: () async {await _auth.signOut();},), ],),
body: Text("My ID is ${currentUser.uid}"),
),);
}
}
//******************************************
class SignIn extends StatefulWidget {
#override _SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
final AuthService _auth = AuthService();
final _formKey = GlobalKey<FormState>();
String error = '';
String email ;
String password ;
#override Widget build(BuildContext context) {
return Scaffold(
body: Form(key: _formKey,
child: ListView(shrinkWrap: true, children: <Widget>[ SizedBox(height: 60.0),
emailField(), SizedBox(height: 8.0),
passwordField(), SizedBox(height: 24.0),
signInButton(),
Text(error, style: TextStyle(color: Colors.red, fontSize: 16.0),),
],),),);}
RaisedButton signInButton() {
return RaisedButton( child: Text('Sign In'),
onPressed: () async {
if(_formKey.currentState.validate()) {
dynamic result = await _auth.signInWithEmailAndPassword(email, password);
//If successful SignIn, the Provider listening in the Wrapper will automatically load the Home page.
if(result == null) {setState(() {error = 'Could not sign in with those credentials';});}}});
}
TextFormField passwordField() {
return TextFormField(
validator: (val) => val.length < 6 ? 'Enter a password 6+ chars long' : null,
onChanged: (val) {setState(() => password = val);},);
}
TextFormField emailField() {
return TextFormField(
validator: (val) => val.isEmpty ? 'Enter an email' : null,
onChanged: (val) {setState(() => email = val);},);
}
}

NoSuchMethodError: The method 'findAncestorStateOfType' was called on null

I've built a simple login page that on clicking the button opens the menu to choose a google account and then navigates to the homepage of the app.
The button gets clicked but the login menu doesn't pop up. It throws an error saying :
E/flutter ( 4974): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Navigator operation requested with a context that does not include a Navigator. E/flutter ( 4974): The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
I tried adding a new context to the tree but then a different error came up:
E/flutter (22865): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: NoSuchMethodError: The method 'findAncestorStateOfType' was called on null. E/flutter (22865): Receiver: null E/flutter (22865): Tried calling: findAncestorStateOfType()
What should I do?
Here's my code:
import 'package:flutter/material.dart';
import 'package:sorted/screens/sortedMain.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class LoginMain extends StatefulWidget {
LoginMain({Key key}) : super(key: key);
#override
_LoginMainState createState() => _LoginMainState();
}
class _LoginMainState extends State<LoginMain> {
final GoogleSignIn _googleSignIn = GoogleSignIn();
final FirebaseAuth _auth = FirebaseAuth.instance;
FirebaseUser _user;
bool isUserSignedIn = false;
#override
void initState() {
super.initState();
checkIfUserIsSignedIn();
}
void checkIfUserIsSignedIn() async {
var userSignedIn = await _googleSignIn.isSignedIn();
setState(() {
isUserSignedIn = userSignedIn;
});
}
Future<FirebaseUser> _handleSignIn() async {
FirebaseUser user;
bool userSignedIn = await _googleSignIn.isSignedIn();
setState(() {
isUserSignedIn = userSignedIn;
});
if (isUserSignedIn) {
user = await _auth.currentUser();
} else {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
user = (await _auth.signInWithCredential(credential)).user;
userSignedIn = await _googleSignIn.isSignedIn();
setState(() {
isUserSignedIn = userSignedIn;
});
}
_user = user;
return user;
}
void onGoogleSignIn(BuildContext context) async {
FirebaseUser user = await _handleSignIn().then((value) => null);
BuildContext context;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SortedMain(_googleSignIn, user)),
);
setState(() {
isUserSignedIn == null ? true : false;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Sorted.'),
backgroundColor: Color(0xff0A3D62),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
const Color(0xFF5761B2),
const Color(0xFF1FC588),
])),
),
),
drawer: null,
body: Container(
padding: EdgeInsets.only(top: 30),
width: 360,
height: 600,
color: const Color(0xFF273748),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 300,
height: 300,
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(20.0),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.white, Colors.grey[400]]),
),
child: MaterialButton(
height: 50.0,
minWidth: 150.0,
color: Colors.green,
splashColor: Colors.teal,
textColor: Colors.white,
child: new Icon(Icons.arrow_forward),
onPressed: () {
BuildContext context;
onGoogleSignIn(context);
},
)),
],
)),
),
),
);
}
}
Try using the code below for setting up your firebase class
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class AuthProvider {
final FirebaseAuth _auth = FirebaseAuth.instance;
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
Future<bool> loginWithGoogle() async {
try {
GoogleSignIn googleSignIn = GoogleSignIn();
GoogleSignInAccount account = await googleSignIn.signIn();
if (account == null) return false;
AuthResult res =
await _auth.signInWithCredential(GoogleAuthProvider.getCredential(
idToken: (await account.authentication).idToken,
accessToken: (await account.authentication).accessToken,
));
if (res.user == null) return false;
return true;
} catch (e) {
print("Error logging with google");
return false;
}
}
Future<void> logOut() async {
try {
await _auth.signOut();
} catch (e) {
print("error logging out");
}
}
}
class User {
final String uid;
User({ this.uid });
}
and try restricting your Navigator with an if statement so it can't navigate without the completion of the login process.
CustomButtom(
onpressed : () async {
bool res = await AuthProvider().loginWithGoogle();
if (res) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SortedMain()));
} else
print("Login failed");
},
),

Firebase Login with Flutter using onAuthStateChanged

Outside of Flutter, when I implement firebase authentication I always use the onAuthStateChanged listener provided by firebase to determine if the user is logged in or not and respond accordingly.
I am trying to do something similar using flutter, but I can find a way to access onAuthStateChanged of Firebase. I am using the firebase_auth, and google_signin Flutter plugins. I am working of example code that is included with the firebase_auth Flutter plugin. Below is the sample code. I can login successfully with google sign in, but the example is too simple, because I want to have an observer/listener to detect the user's signed in/out state.
Is there a way to detect via observer/listener using the firebase_auth/google_signin flutter plugins to determine the status of a user?
Ultimately I want the app to determine if the user is logged in (yes/no). If not then show a login screen, if yes then show my main app page.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = new GoogleSignIn();
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Firebase Auth Demo',
home: new MyHomePage(title: 'Firebase Auth Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<String> _message = new Future<String>.value('');
Future<String> _testSignInAnonymously() async {
final FirebaseUser user = await _auth.signInAnonymously();
assert(user != null);
assert(user == _auth.currentUser);
assert(user.isAnonymous);
assert(!user.isEmailVerified);
assert(await user.getToken() != null);
if (Platform.isIOS) {
// Anonymous auth doesn't show up as a provider on iOS
assert(user.providerData.isEmpty);
} else if (Platform.isAndroid) {
// Anonymous auth does show up as a provider on Android
assert(user.providerData.length == 1);
assert(user.providerData[0].providerId == 'firebase');
assert(user.providerData[0].uid != null);
assert(user.providerData[0].displayName == null);
assert(user.providerData[0].photoUrl == null);
assert(user.providerData[0].email == null);
}
return 'signInAnonymously succeeded: $user';
}
Future<String> _testSignInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final FirebaseUser user = await _auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getToken() != null);
return 'signInWithGoogle succeeded: $user';
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new MaterialButton(
child: const Text('Test signInAnonymously'),
onPressed: () {
setState(() {
_message = _testSignInAnonymously();
});
}),
new MaterialButton(
child: const Text('Test signInWithGoogle'),
onPressed: () {
setState(() {
_message = _testSignInWithGoogle();
});
}),
new FutureBuilder<String>(
future: _message,
builder: (_, AsyncSnapshot<String> snapshot) {
return new Text(snapshot.data ?? '',
style: const TextStyle(
color: const Color.fromARGB(255, 0, 155, 0)));
}),
],
),
);
}
}
Here are links to the flutter packages in question:
https://github.com/flutter/plugins/tree/master/packages/firebase_auth
https://github.com/flutter/plugins/tree/master/packages/google_sign_in
I know this question is pretty old, but here is the answer if anybody is still looking for it.
Firebase returns a Stream of FirebaseUser with it's onAuthStateChanged function. There are many ways to listen to the user's authentication state change. This is how I do it:
Solution 1
I return a StreamBuilder to my App's home page, and the StreamBuilder returns specific pages based on the auth status of the user.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Your App Name',
home: _getLandingPage()
);
}
Widget _getLandingPage() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.providerData.length == 1) { // logged in using email and password
return snapshot.data.isEmailVerified
? MainPage()
: VerifyEmailPage(user: snapshot.data);
} else { // logged in using other providers
return MainPage();
}
} else {
return LoginPage();
}
},
);
}
Solution 2
You can create a listener in your app's initState() function as well. Make sure the firebase app has been initialized before registering the listener.
#override
void initState() {
super.initState();
FirebaseAuth.instance.authStateChanges().listen((firebaseUser) {
// do whatever you want based on the firebaseUser state
});
}
Solution 3 (Update May 2021)
A simple approach with null-safety without using the provider package:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
/// State is persistent and not rebuilt, therefore [Future] is only created once.
/// If [StatelessWidget] is used, in the event where [App] is rebuilt, that
/// would re-initialize FlutterFire and makes our app re-enter the
/// loading state, which is undesired.
class _AppState extends State<App> {
final Future<FirebaseApp> _initFirebaseSdk = Firebase.initializeApp();
final _navigatorKey = new GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: _navigatorKey,
theme: theme(),
home: FutureBuilder(
future: _initFirebaseSdk,
builder: (_, snapshot) {
if (snapshot.hasError) return ErrorScreen();
if (snapshot.connectionState == ConnectionState.done) {
// Assign listener after the SDK is initialized successfully
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null)
_navigatorKey.currentState!
.pushReplacementNamed(LoginScreen.routeName);
else
_navigatorKey.currentState!
.pushReplacementNamed(HomeScreen.routeName);
});
}
return LoadingScreen();
}),
routes: routes,
);
}
}
This approach guarantees that you only use Firebase authentication FirebaseAuth.instance.authStateChanges().listen() after the SDK completes initialization. The auth change listener will be first invoked on app launch and then automatically called again after logout and login.
.pushReplacementNamed() will move to a new screen without back (no back icon on the app bar)
Null safe code (without 3rd party packages)
Screenshot:
To check if the user is signed in from anywhere in the app, use
bool signedIn = Auth.instance.isSignedIn;
To sign in, use
await Auth.instance.signIn(email: 'email', password: 'password');
To sign out, use
await Auth.instance.signOut();
Full Code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(
home: StreamBuilder<User?>(
stream: Auth.instance.authStateChange(),
builder: (_, snapshot) {
final isSignedIn = snapshot.data != null;
return isSignedIn ? HomePage() : LoginPage();
},
),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Center(
child: ElevatedButton(
onPressed: () => Auth.instance.signOut(),
child: Text('Sign out'),
),
),
);
}
}
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Auth.instance.signIn(email: 'test#test.com', password: 'test1234'),
child: Text('Sign in'),
),
),
);
}
}
class Auth {
static final instance = Auth._();
Auth._();
final FirebaseAuth _auth = FirebaseAuth.instance;
bool get isSignedIn => _auth.currentUser != null;
Stream<User?> authStateChange() => _auth.authStateChanges();
Future<void> signIn({required String email, required String password}) => _auth.signInWithEmailAndPassword(email: email, password: password);
Future<void> signOut() => _auth.signOut();
}
Same code using provider package:
Check this answer:
You can create a stream as a getter for the onAuthStateChanged inside an AuthService class. To help you manage the state, you can use the Provider package. The AuthService class will extend the ChangeNotifier class.
class AuthService extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = new GoogleSignIn();
// create a getter stream
Stream<FirebaseUser> get onAuthStateChanged => _auth.onAuthStateChanged;
//Sign in async functions here ..
}
Wrap your MaterialApp with ChangeNotifierProvider and return an instance of the AuthService class in create method like so:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => AuthService(),
child: new MaterialApp(
title: 'Firebase Auth Demo',
home: Landing(),
),
);
}
}
Now create landing page as a stateless widget. Use Provider.of(context) and a stream builder to listen to the auth changes and render the login page or home page as appropriate.
class Landing extends StatelessWidget {
#override
Widget build(BuildContext context) {
AuthService auth = Provider.of<AuthService>(context);
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user == null) {
return LogIn();
}
return Home();
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
}
You can read more about state management with provider from the official flutter documentation. Follow this link: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
The Firebase for Flutter Codelab has a much more in-depth example using Google sign in and Firebase auth.
After the final step you end up with this _ensureLoggedIn function that is used to check whether the user is signed in and if not, initiate a sign in flow.
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = googleSignIn.currentUser;
if (user == null)
user = await googleSignIn.signInSilently();
if (user == null) {
user = await googleSignIn.signIn();
analytics.logLogin();
}
if (auth.currentUser == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
}
You could modify this to check these things when your app starts up and conditionally show different views to pre-auth and post-auth users with something like:
final auth = FirebaseAuth.instance;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'MyApp',
home: (_checkLogin() == true ? new PostAuthScaffold() : new PreAuthScaffold())
);
}
}
bool _checkLogin() {
GoogleSignInAccount user = googleSignIn.currentUser;
return !(user == null && auth.currentUser == null);
}
Null safe code (using provider):
Full Code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(
home: ChangeNotifierProvider(
create: (_) => AuthModel(),
child: Consumer<AuthModel>(
builder: (_, model, __) => model.isSignedIn ? HomePage() : LoginPage(),
),
),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Center(
child: ElevatedButton(
onPressed: () async {
final model = context.read<AuthModel>();
await model.signOut();
},
child: Text('Sign out'),
),
),
);
}
}
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () async {
final model = context.read<AuthModel>();
await model.signIn(email: 'test#test.com', password: 'test1234');
},
child: Text('Sign in'),
),
),
);
}
}
class AuthModel extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
bool get isSignedIn => _auth.currentUser != null;
Future<void> signIn({required String email, required String password}) async {
await _auth.signInWithEmailAndPassword(email: email, password: password);
notifyListeners();
}
Future<void> signOut() async {
await _auth.signOut();
notifyListeners();
}
}
I had the same query I used Shared preference to get the auth state changed information I also have built a project using Shared Prefrences with Firebase and flutter. Iif you wish to know you can read the blog written on the same by me :
Implementing FirebaseAuthStateListener using Shared Prefrences in Flutter.” https://medium.com/#vaibhavminiyar/implementing-firebaseauthstatelistener-using-shared-prefrences-in-flutter-b42e12f81eb2
since the last update of FlutterFire you need to do it like this
FirebaseAuth.instance.authStateChanges().listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
}
);
Firebase Auth enables you to subscribe in realtime to this state via a
Stream. Once called, the stream provides an immediate event of the
users current authentication state, and then provides subsequent
events whenever the authentication state changes.
To subscribe to these changes, call the authStateChanges() method on
your FirebaseAuth instance.
The stream returns a User class if the user is signed in, or null
if they are not. You can read more about managing your users below.
More information

Resources