I have a WelcomeScreen
which contains sign up and login and the HomeScreen
where I want to redirect after the user logs in. To manage auth data, I have created an auth.dart
with static
properties and methods so I can access them across all pages with same data.
import 'package:firebase_auth/firebase_auth.dart';
class Auth {
static final auth = FirebaseAuth.instance;
static Future<void> logout() async {
await auth.signOut();
}
static Future<void> loginUser(String userEmail, String userPassword) async {
await auth.signInWithEmailAndPassword(email: userEmail, password: userPassword);
}
static Future<FirebaseUser> getCurrentUser() async {
return await auth.currentUser();
}
}
In main.dart
file, I am using StreamBuilder
to change the current screen based on changing auth data. I got this StreamBuilder
code from this answer.
home: StreamBuilder<FirebaseUser>(
stream: Auth.auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) {
return HomeScreen();
} else {
return WelcomeScreen();
}
},
),
In my login screen, I am using the below code to trigger log in:
Future<void> login() async {
...
try {
await Auth.loginUser(userEmail, userPassword);
var user = await Auth.getCurrentUser();
print(user.displayName); // This works
} catch (error) {
print(error.message);
}
}
I don't know whether the static methods I am using are the correct way to handle Firebase auth or not but it seems to work. After logging in, I am able to display the name of the logged in user but the StreamBuilder
in main.dart
is not reflecting the updated auth data, i.e not changing the page.
Is it because of static methods or something wrong in the implementation of StreamBuilder
?
Firebase Auth provides many methods and utilities for enabling you to integrate secure authentication into your new or existing Flutter application. In many cases, you will need to know about the authentication state of your user, such as whether they're logged in or logged out.
Firebase Auth enables you to subscribe in realtime to this state via a Stream. Once called, the stream provides an immediate event of the user's current authentication state, and then provides subsequent events whenever the authentication state changes. There are three methods for listening to authentication state changes:
To configure these settings, call the setPersistence () method (note; on native platforms an UnimplementedError will be thrown): Firebase provides a number of ways to sign users into your application, from anonymous users, password authentication, phone authentication and using OAuth/social providers.
To get started, call the signInAnonymously () method on the FirebaseAuth instance: UserCredential userCredential = await FirebaseAuth.instance.signInAnonymously (); Once successfully resolved, the user will be granted an anonymous account. If you are listening to changes in authentication state, a new event will be sent to your listeners.
Screenshot:
I'm not sure how you were doing it, so I added a minimal working code, I didn't make any changes to your Auth
class. Although it is a good idea to use Provider
but you can get things done with static
method also.
Edited code:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: Auth.auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) return HomeScreen();
else return WelcomeScreen();
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Screen')),
floatingActionButton: FloatingActionButton.extended(
label: Text('Sign out'),
onPressed: Auth.logout,
),
);
}
}
class WelcomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Welcome Screen')),
body: Center(
child: RaisedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => LoginPage())),
child: Text('Go to Login Page'),
),
),
);
}
}
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login Page')),
body: Center(
child: RaisedButton(
onPressed: () async {
await Auth.loginUser('[email protected]', 'test1234');
await Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => MyApp()), (_) => false);
},
child: Text('Login'),
),
),
);
}
}
In my opinion the best way to manage firebase authentication in flutter is to use the provider package. Your Auth class is missing one important thing which is the onAuthStateChnaged method. You can create a stream as a getter for the onAuthStateChanged inside an Auth class. The Auth class will extend the ChangeNotifier class. ChangeNotifier class is part of the flutter api.
class Auth extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
// create a getter stream
Stream<FirebaseUser> get onAuthStateChanged => _auth.onAuthStateChanged;
//Sign in async functions here ..
}
Wrap your MaterialApp with ChangeNotifierProvider (part of provider package) and return an instance of the Auth class in create method like so:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Auth(),
child: new MaterialApp(
home: Landing(),
),
);
}
}
Now create landing page as a stateless widget. Use a Consumer or 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) {
Auth auth = Provider.of<Auth>(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
I made a video(https://youtu.be/iqy7xareuAI) discussing this bounty and taking you through the steps of implementing the app that you want. All it needs is a simple StreamBuilder
and a FutureBuilder
.
More complex tools like provider
and singleton pattern
(what you are trying to achieve with static classes) can be applied for more complex applications, but not needed here.
Here is the code for the WelcomeScreen:
import 'package:ctfultterfireexperiments/src/screens/home_screen.dart';
import 'package:ctfultterfireexperiments/src/screens/login_signup_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class WelcomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext _, AsyncSnapshot<FirebaseUser> snapshot) {
//if the snapshot is null, or not has data it is signed out
if(! snapshot.hasData) return LoginSignupScreen();
// if the snapshot is having data it is signed in, show the homescreen
return HomeScreen();
},
);
}
}
Here is the code for HomeScreen.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
child: Center(
child: FutureBuilder(
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if(!snapshot.hasData) return LinearProgressIndicator();
return Text("Home Screen: ${snapshot.data.displayName}");
},
future: FirebaseAuth.instance.currentUser(),
)),
),
Spacer(),
RaisedButton(onPressed: () {FirebaseAuth.instance.signOut();})
],
);
}
}
Here is the code for LoginSignupScreen.dart:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
class LoginSignupScreen extends StatelessWidget {
login() async{
final GoogleSignIn _googleSignIn = GoogleSignIn();
final _auth = FirebaseAuth.instance;
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final FirebaseUser user = (await _auth.signInWithCredential(credential)).user;
print("signed in " + user.displayName);
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Spacer(flex: 1,),
Text("Login/Signup Screen"),
Spacer(flex: 2,),
RaisedButton(onPressed: login)
],
);
}
}
This will work as a minimum working example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With