通过电子邮件/密码进行身份验证的颤振问题

Posted

技术标签:

【中文标题】通过电子邮件/密码进行身份验证的颤振问题【英文标题】:Flutter issue with authentication via Email/Password 【发布时间】:2020-11-23 18:39:33 【问题描述】:

我是 Flutter 的新手,我正在尝试构建一个具有登录和注册功能的身份验证系统。 我遵循了在线教程,一切正常。但我的身份验证流程略有不同,当我添加额外步骤时,登录或注册屏幕不会重定向到主页登录页面。

这是教程中的代码,运行良好。您会注意到,当应用程序启动时,出现的第一个屏幕是 Wrapper.dart 通过 IF 语句调用的 SignIn。

这里是教程中的文件:tutorial main.dart

void main() 
  runApp(Main());


class Main extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return StreamProvider<User>.value(
        value: AuthService().user,
        child: MaterialApp(
          home: Wrapper(),
          debugShowCheckedModeBanner: false,
        )
      );
  


教程 wrapper.dart

class Wrapper extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    final user = Provider.of<User>(context);

    // return either Home or Auth widget
    if (user == null) 
      return Authenticate();
     else 
      return Home();
    
  

tutorial authenticate.dart

class Authenticate extends StatefulWidget 
  @override
  _AuthenticateState createState() => _AuthenticateState();


class _AuthenticateState extends State<Authenticate> 
  bool showSignIn = true;
  void toggleView() 
    setState(() => showSignIn = !showSignIn);
  

  @override
  Widget build(BuildContext context) 
    if (showSignIn) 
      return SignIn(toggleView: toggleView);
     else 
      return Register(toggleView: toggleView);
    
  

教程 signIn.dart

class SignIn extends StatefulWidget 
  final Function toggleView;
  SignIn(this.toggleView);

  @override
  _SignInState createState() => _SignInState();


class _SignInState extends State<SignIn> 
  final AuthService _auth = AuthService();
  final _formKey = GlobalKey<FormState>();
  bool loading = false;

  // text Field State
  String email = '';
  String password = '';
  String error = '';

  @override
  Widget build(BuildContext context) 
    return Scaffold(
        appBar: AppBar(
            backgroundColor: Colors.brown[400],
            elevation: 0.0,
            title: Text('Sign in'),
            actions: <Widget>[
              FlatButton.icon(
                icon: Icon(Icons.person),
                label: Text('Register'),
                onPressed: () 
                  widget.toggleView();
                ,
              )
            ]),
        body: Container(
            padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 50.0),
            child: Form(
                key: _formKey,
                child: Column(children: <Widget>[
                  SizedBox(height: 20.0),
                  TextFormField(
                    decoration: textInputDecoration.copyWith(hintText: 'Email'),
                    validator: (val) => val.isEmpty ? 'Enter an email' : null,
                    onChanged: (val) 
                      setState(() => email = val);
                    ,
                  ),
                  SizedBox(height: 20.0),
                  TextFormField(
                    obscureText: true,
                    decoration:
                        textInputDecoration.copyWith(hintText: 'Password'),
                    validator: (val) => val.length < 6
                        ? 'Password is too short. Min 6 characters long'
                        : null,
                    onChanged: (val) 
                      setState(() => password = val);
                    ,
                  ),
                  SizedBox(height: 20.0),
                  RaisedButton(
                    color: Colors.pink[400],
                    child: Text(
                      'Sign in',
                      style: TextStyle(color: Colors.white),
                    ),
                    onPressed: () async 
                      if (_formKey.currentState.validate()) 
                        setState(() => loading = true);

                        dynamic result = await _auth.signInWithEmailAndPassword(
                            email, password);
                        if (result == null) 
                          setState(() 
                            error =
                              'These credentials are invalid. Please check them and try again!';
                            loading = false;
                          );
                        
                      
                    ,
                  ),
                ]))
            ));
  


Registration.dart 文件与SignIn 类似,这里就不展示了。 最后一个需要记录的文件是名为 AuthService.dart

的服务
class AuthService 
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // Create 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); // This is the equivalent of .map((FirebaseUser user) => _userFromFirebaseUser(user));
  

  // SignIn with Email & Password
  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;
    
  


让我解释一下上述流程的工作原理 当用户启动应用程序时,main.dart 文件调用wrapper.dart,它检查用户是否为空或存在。 如果User=null,则表示它没有登录并调用Authenticate.dart,默认情况下会打开SignIn屏幕。 如果User != null,则表示用户已登录并调用Home.dart 屏幕。 ****** 我的问题 ***** 现在,在我的流程中(见下文),WrapperUser=null 时调用StartAuthButtons.dartStartAuthButtons 有几个按钮,可以使用不同的方法对用户进行身份验证,例如 Google、Facebook、Apple 和电子邮件/密码。

我的 Wrapper.dart

class Wrapper extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    final user = Provider.of<User>(context);

    if (user == null) 
      return StartAuthButtons();
      //return Authenticate(); <-- this was before
     else 
      return Home();
    
  

一旦用户点击使用电子邮件和密码登录,我使用 Flutter Navigate 进入登录页面。这是我与教程不同的额外步骤。 (请在下面的代码中检查我的 cmets) 我的 StartAuthButtons.dart

class StartAuthButtons extends StatefulWidget 
  @override
  _StartAuthButtonsState createState() => _StartAuthButtonsState();


class _StartAuthButtonsState extends State<StartAuthButtons> 
  final AuthService _auth = AuthService();

  @override
  Widget build(BuildContext context) 
    return Stack(children: [
            AuthLayout(),
            Scaffold(
                backgroundColor: Colors.transparent,
                appBar: AppBar(
                  title: Center(child: Text('Welcome')),
                  backgroundColor: Colors.transparent,
                ),
                body: SingleChildScrollView(
                  child: ConstrainedBox(
                    constraints: BoxConstraints(),
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: <Widget>[

                          //Email & Password SignIn
                          Padding(
                            padding: EdgeInsets.all(8.0), child: new MaterialButton(
                              child: SignInEmailPasswordBtn(),
                              onPressed: () 
                                ****** I THINK THE ISSUE IS HERE *********
                                Navigator.push(
                                  context,
                                  MaterialPageRoute(builder: (context) => Authenticate()),
                                );

                                *** As you can see above, here is where I call Authenticate() ***
                                ****** But by adding this extra step to reach the SignIn page, when I signin it doesn't redirect to the Home once signed in. It stays on the SignIn screen. If I hard refresh the app, the system is logged in **********

                                
                              
                            )
                          )
                        ]
                      )
                    )
                  )
            ))]);
    
  

使用Navigate 小部件转到SignIn 页面后,我在成功登录后似乎无法转到Home 屏幕。 身份验证工作正常,因为如果我硬刷新应用程序,它会以登录用户的身份显示Home 屏幕。

似乎onPress 进行身份验证,在HomeStartAuthButton 之间选择的IF STATEMENT 没有被触发。

非常感谢任何帮助。 抱歉,脚本太长了。 乔

【问题讨论】:

这种行为的原因是您将多个小部件相互堆叠,这将不允许 Provider 到达它。我想说你需要做的就是pop 离开stack。看看这篇文章:medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31 @Frank van Puffelen ,我添加了 Navigator.of(context).maybePop();到登录按钮 onPress。一旦我登录,它确实会显示主页,但是当我点击登录时,屏幕会在转到Home 之前弹回“StartAuthButtons”。我的加载小部件甚至没有被考虑在内。有什么想法可以避免显示 StartAuthButtons 小部件并查看加载吗? (在 SignIn 小部件中的代码下方) 这里代码更新:onPressed: () async if (_formKey.currentState.validate()) setState(() => loading = true); Navigator.of(context).maybePop();等等…… 是@Unbreachable 发表了该评论。我只是编辑了你问题的标签。 【参考方案1】:

感谢@Unbreachable 建议查看文章以进行pop & push。

我对两个提交 onPress 按钮上的 SignInRegister 小部件进行了简单的更改。

之前的代码:

onPressed: () async 
   if (_formKey.currentState.validate()) 
     setState(() => loading = true);
                                          
    dynamic result = await _auth.registerWithEmailAndPassword(email, password);

    if (result == null) 
      setState(() 
          error = 'Email or password are invalid.';
          loading = false;
      );
    
  
,

之后:

onPressed: () async 
   if (_formKey.currentState.validate()) 
     setState(() => loading = true);
                                          
    dynamic result = await _auth.registerWithEmailAndPassword(email, password);

    if (result == null) 
      setState(() 
          error = 'Email or password are invalid.';
          loading = false;
      );
     else 
      Navigator.of(context).pop(); <-- here the change. 
      loading = false; <-- here the change.
    
  
,

现在加载工作正常,当消失时我可以看到 home 小部件。

【讨论】:

以上是关于通过电子邮件/密码进行身份验证的颤振问题的主要内容,如果未能解决你的问题,请参考以下文章

使用邮件和密码通过 REST API [Firebase] 进行身份验证

django rest framework jwt 使用电子邮件和密码进行身份验证

用于电子邮件链接身份验证的颤振 ERROR_INVALID_ACTION_CODE 中的 firebase 身份验证

在不使用 Firebase 的情况下使用电子邮件和密码进行 Flutter 身份验证

Flutter 在尝试 Firebase 身份验证时无法捕获 PlatformException

在颤振应用程序的firebase身份验证中合并两个uid