问题
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
?
回答1:
[I am adding this as a separate answer because it uses provider package]
Instead of making your variables and methods static
in Auth
class, you should use Provider
or some other models. I am using a bool
variable in Auth
class to keep track of login, you can modify the class according to your needs.
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ChangeNotifierProvider<Auth>(
create: (_) => Auth(),
child: MaterialApp(home: MyApp()),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<Auth>(
builder: (_, auth, __) {
if (auth.loggedIn) return HomeScreen();
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: () async {
final auth = Provider.of<Auth>(context, listen: false);
await 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.pushReplacement(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 {
final auth = Provider.of<Auth>(context, listen: false);
await auth.loginUser('test@test.com', 'test1234');
await Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => MyApp()));
},
child: Text('Login'),
),
),
);
}
}
class Auth with ChangeNotifier {
final _auth = FirebaseAuth.instance;
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
Future<void> logout() async {
await _auth.signOut();
_loggedIn = false;
notifyListeners();
}
Future<void> loginUser(String userEmail, String userPassword) async {
await _auth.signInWithEmailAndPassword(email: userEmail, password: userPassword);
_loggedIn = true;
notifyListeners();
}
Future<FirebaseUser> getCurrentUser() async {
return await _auth.currentUser();
}
}
Now you can see, even when you're logging in from LoginPage
which isn't a direct child of your Consumer
, you can see its builder
is still getting called when login status changes. However, when you navigate to LoginPage
, you need to make a pop to get back to the previous page because your widget tree was displaying LoginPage
only. In my previous example I was navigating to HomeScreen
, but in this one I am popping.
回答2:
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('test@test.com', 'test1234');
await Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => MyApp()), (_) => false);
},
child: Text('Login'),
),
),
);
}
}
回答3:
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
回答4:
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.
- We treat WelcomeScreen as the screen that decides between LoginSignupScreen and HomeScreen
- We use StreamBuilder for WelcomeScreen
- We use FutureBuilder for HomeScreen.
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.
来源:https://stackoverflow.com/questions/61931976/how-to-manage-firebase-authentication-state-in-flutter