基于是新登录还是返回用户在 Flutter 中导航屏幕

Posted

技术标签:

【中文标题】基于是新登录还是返回用户在 Flutter 中导航屏幕【英文标题】:Navigating Screens in Flutter based on if it's new sign in or returning user 【发布时间】:2021-02-27 20:26:03 【问题描述】:

注册后,我希望用户被带到 WelcomeScreen() 并在登录后被带到 Sessions()。但是,以我的根页面目前的结构方式,实现起来有点困难。

我该怎么做?请参阅下面的代码 sn-ps 和说明。

在我的 main.dart 文件中,我的 RootPage 设置为 initialRoute。 RootPage 检查是否存在有效会话并因此重定向到 PaymentScreen,否则重定向到处理登录和注销视图的 Sessions.dart。

//root_page.dart
// ignore: must_be_immutable
class RootPage extends StatelessWidget 
  static const String id = 'root_page';

  Widget buildWaitingScreen() 
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
      ),
    );
  

  @override
  // ignore: missing_return
  Widget build(BuildContext context) 
    final BaseAuth auth = AuthProvider.of(context).auth;
    return StreamBuilder<String>(
      stream: auth.onAuthStateChanged,
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) 
        if (snapshot.connectionState == ConnectionState.active) 
          final bool isLoggedIn = snapshot.hasData;

          return isLoggedIn ? WelcomeEmail() : Sessions();
//          return isLoggedIn ? PaymentScreen() : Sessions();
        
        return buildWaitingScreen();
      ,
    );
  

我的 AuthProvider...

class AuthProvider extends InheritedWidget 
  AuthProvider(
    Key key,
    @required this.auth,
    @required Widget child,
  )  : assert(auth != null),
        assert(child != null),
        super(key: key, child: child);

  final BaseAuth auth;

  @override
  bool updateShouldNotify(AuthProvider old) => auth != old.auth;

  //this static class allows us to access AuthProvider anywhere in our code using AuthProvider.of(context).auth
  static AuthProvider of(BuildContext context) 
    return context.dependOnInheritedWidgetOfExactType<AuthProvider>();
  

我的 auth.dart

abstract class BaseAuth 
  Stream<User> get user;
  Stream<String> get onAuthStateChanged;
  Future<String> handleSignIn(String email, String password);
  Future<String> handleSignUp(String email, String password);
  Future<String> currentUser();


class Auth implements BaseAuth 
  final _auth = FirebaseAuth.instance;

  //create a new 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);
  

  @override
  Stream<String> get onAuthStateChanged 
    return _auth.onAuthStateChanged.map((user) => user?.uid);
  

  //sign in with email and password
  Future<String> handleSignIn(String email, String password) async 
    AuthResult result = await FirebaseAuth.instance
        .signInWithEmailAndPassword(email: email, password: password)
        .catchError((error) => print(error));

    if (result != null) 
      FirebaseUser user = result.user;
      if (user != null) 
        print('From handleSignIn, Log in: $user.email');
      
      return user.uid;
    
  

  //sign up with email and password
  Future<String> handleSignUp(String email, String password) async 
    AuthResult result = await FirebaseAuth.instance
        .createUserWithEmailAndPassword(email: email, password: password)
        .catchError((error) => print(error));
    FirebaseUser user = result.user;
    if (user != null) 
      print('From handleSignUp, New user email: $user.email');
    

    return user.uid;
  

我的 session.dart 文件

class Sessions extends StatefulWidget 
  static const String id = 'sessions';

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


enum SessionType  signIn, signUp, reset 

class _SessionsState extends State<Sessions> 
  String email, password, _warning;

  bool showSpinner = false;
  final _formKey = GlobalKey<FormState>();
  var args;

  SessionType _sessionType = SessionType.signIn;

  setArguments(args) async 
    _sessionType = args[0];
  

  //validate form fields input and save
  bool validateAndSave() 
    final form = _formKey.currentState;
    if (form.validate()) 
      form.save();
      print('form is valid, Email: $email, Password: $password');
      return true;
     else 
      print('form is invalid');
      return false;
    
  

  //remove extra spacing before and after input text
  String trimTextInput(String value) 
    return value.toString().trim();
  

  //validate form and sign in / sign up
  void validateAndSubmit() async 
    if (validateAndSave()) 
      setState(() 
        showSpinner = true;
      );
      try 
        final BaseAuth auth = AuthProvider.of(context).auth;
        if (_sessionType == SessionType.signIn) 
          String user = await auth.handleSignIn(
              trimTextInput(email), trimTextInput(password));
          print('Signed in: $user');

          if (user == null) 
            setState(() 
              showSpinner = false;
              _warning = 'could not sign in with those credentials';
            );
          
         else if (_sessionType == SessionType.reset) 
          print('password reset email sent to $email');
          await auth
              .sendPasswordResetEmail(trimTextInput(email))
              .catchError((onError) => print(onError));
          _warning = 'A password reset link has been sent to $email';
          setState(() 
            showSpinner = false;
            _sessionType = SessionType.signIn;
          );
         else 
          String user = await auth.handleSignUp(
              trimTextInput(email), trimTextInput(password));
          print('Signed up: $user');
          if (user == null) 
            setState(() 
              showSpinner = false;
              _warning = 'could not sign up with those credentials';
            );
          
        
       catch (e) 
        print('Warning message: $e.toString()');
      
    
  

  //toggle to switch between sign up and sign in views
  void toggleFormView(String state) 
    _formKey.currentState.reset();
    if (state == 'signUp') 
      setState(() 
        _sessionType = SessionType.signUp;
      );
     else 
      setState(() 
        _sessionType = SessionType.signIn;
      );
    
  

  @override
  void initState() 
    super.initState();
    Future.delayed(Duration.zero, () 
      setState(() 
        args = ModalRoute.of(context).settings.arguments;
      );
      // _sessionType = args.sessionType;
      print('Session type: $_sessionType');
      // setArguments(args);
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        child: ModalProgressHUD(
          inAsyncCall: showSpinner,
          child: SafeArea(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                showAlert(),
                buildHeaderText(_warning, 3, kHeading1TextStyle),
                SizedBox(height: 8.0),
                Flexible(
                  child: Hero(
                    tag: 'logo',
                    child: Container(
                      height: 80.0,
                      child: Image.asset('images/logo.png'),
                    ),
                  ),
                ),
                SizedBox(height: 7.0),
                Container(
                  padding: EdgeInsets.symmetric(horizontal: 20.0),
                  child: Form(
                    key: _formKey,
                    child: Column(
                      children: buildInputFields() + submitButtons(),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  

  Widget showAlert() 
    if (_warning != null) 
      return Container(
        alignment: Alignment.topCenter,
        color: Colors.amberAccent,
        width: double.infinity,
        padding: EdgeInsets.all(8.0),
        child: Row(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(right: 8.0),
              child: Icon(Icons.error_outline),
            ),
            Expanded(
              child: warningText(),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 8.0),
              child: IconButton(
                icon: Icon(Icons.close),
                onPressed: () 
                  setState(() 
                    _warning = null;
                  );
                ,
              ),
            )
          ],
        ),
      );
    
    return SizedBox(
      height: 0,
    );
  

  Widget warningText() 
    return Text(
      _warning,
      textAlign: TextAlign.center,
      style: kRegularTextStyle,
      maxLines: 3,
    );
  

  Widget buildHeaderText(String headerText, int maxLines, TextStyle style) 
    if (_sessionType == SessionType.signUp) 
      headerText = "Create New Account";
     else if (_sessionType == SessionType.reset) 
      headerText = "Reset Password";
     else 
      headerText = "Sign In";
    
    return Text(
      headerText,
      textAlign: TextAlign.center,
      style: style,
      maxLines: maxLines,
    );
  

  List<Widget> buildInputFields() 
    if (_sessionType == SessionType.reset) 
      return [
        TextFormField(
          key: Key('email'),
          keyboardType: TextInputType.emailAddress,
          textAlign: TextAlign.left,
          onSaved: (value) => email = value,
          validator: EmailFieldValidator.validate,
          decoration:
              kTextFieldDecoration.copyWith(hintText: 'enter your email'),
        ),
      ];
     else 
      return [
        TextFormField(
          key: Key('email'),
          keyboardType: TextInputType.emailAddress,
          textAlign: TextAlign.left,
          onSaved: (value) => email = value,
          validator: EmailFieldValidator.validate,
          decoration:
              kTextFieldDecoration.copyWith(hintText: 'enter your email'),
        ),
        SizedBox(height: 8.0),
        TextFormField(
          key: Key('password'),
          obscureText: true,
          textAlign: TextAlign.left,
          validator: PasswordFieldValidator.validate,
          onSaved: (value) => password = value,
          decoration:
              kTextFieldDecoration.copyWith(hintText: 'enter your password'),
        ),
        SizedBox(height: 8.0),
      ];
    
  

  List<Widget> submitButtons() 
    bool _showForgotPassword = false;

    if (_sessionType == SessionType.signIn) 
      _showForgotPassword = true;
      return [
        Buttons(
          key: Key('signIn'),
          buttonLabel: 'Sign In',
          buttonColour: kThemeStyleButtonFillColour,
          buttonTextStyle: kThemeStyleButton,
          onPressedButton: validateAndSubmit,
        ),
        showForgotPassword(_showForgotPassword),
        PaddedClickableText(
          myText: 'Don\'t have an account? Sign up now',
          onTap: () => toggleFormView('signUp'),
        ),
      ];
     else if (_sessionType == SessionType.reset) 
      return [
        Buttons(
          key: Key('submit'),
          buttonLabel: 'Submit',
          buttonColour: kThemeStyleButtonFillColour,
          buttonTextStyle: kThemeStyleButton,
          onPressedButton: validateAndSubmit,
        ),
        PaddedClickableText(
          myText: 'Return back to sign in',
          onTap: () => toggleFormView('signIn'),
        ),
      ];
     else 
      return [
        Buttons(
          key: Key('signUp'),
          buttonLabel: 'Sign Up',
          buttonColour: kThemeStyleButtonFillColour,
          buttonTextStyle: kThemeStyleButton,
          onPressedButton: validateAndSubmit,
        ),
        PaddedClickableText(
          myText: 'Already have an account? Sign in here',
          onTap: () => toggleFormView('signIn'),
        ),
      ];
    
  

  Widget showForgotPassword(bool visible) 
    return Visibility(
      visible: visible,
      child: FlatButton(
        onPressed: () 
          setState(() 
            _sessionType = SessionType.reset;
          );
        ,
        child: Text(
          'Forgot password?',
          textAlign: TextAlign.center,
          style: TextStyle(
            color: kThemeTextColour,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  


class EmailFieldValidator 
  static String validate(String value) 
    return value.isEmpty ? 'Email can\'t be empty' : null;
  


class PasswordFieldValidator 
  static String validate(String value) 
    return value.length < 6 || value.isEmpty
        ? 'Password can\'t be empty'
        : null;
  


class SessionsArguments 
  final SessionType sessionType;

  SessionsArguments(this.sessionType);

【问题讨论】:

那么您所采取的方法面临的问题是什么? 问题是,我正在尝试将用户(如果它是新注册的)导航到欢迎屏幕,而如果它是返回用户(登录操作),我将它们导航到PaymentScreen() 我试过的和上面一样。 为了强调,我试图检测登录用户在我的根页面中的导航位置,并能够将它们导航到 WelcomeScreen() if SignUp 和 PaymentScreen() if SignIn。 【参考方案1】:

通常,人们使用共享首选项来存储基本登录信息, 这样下次有人打开应用程序时,您就可以使用该信息路由到您想要的任何方式。

另一种方法是使用变量。如果他们注册,则将该变量设置为 true 并路由到不同的页面。

如果您需要每个代码 sn-ps,请告诉我。

【讨论】:

嗨,Ayush,声明变量的思路是有道理的。我还没有探索过 SharedPreferences。是的,我很乐意看到代码 sn-ps。谢谢!【参考方案2】:

正如 Ayush 所说,您可以使用 SharedPreferences 来检查用户是否被返回。

为此,首先需要安装shared_preferences 包。 安装很简单:打开 pubspec.yaml 文件,在dependencies 部分下添加这一行shared_preferences: ^0.5.12+4(这是我写这个答案时的最新版本)并运行flutter pub get

一个基本的例子:

// Import shared_preferences library  
import 'package:shared_preferences/shared_preferences.dart';

// Save the isLoggedIn data when user is logged in to system
saveLoggedIn() async 
  SharedPreferences prefs = await SharedPreferences.getInstance();
  prefs.setBool('isLoggedIn', isLoggedIn);


// Get the isLoggedIn data to check if user is returned
getLoggedIn() async 
  SharedPreferences prefs = await SharedPreferences.getInstance();
  bool isUserLoggedIn = prefs.getBool('isLoggedIn');
  return isUserLoggedIn;

【讨论】:

您好,harundemir918,感谢您提供代码 sn-p。它与我上面的内容有什么不同?除非我弄错了,否则我的 auth.onAuthStateChanged 会做同样的事情。此外,它并没有解决我想知道用户是返回还是新用户的问题。 SharedPreferences 就像一个小型本地数据库,您可以在其中存储简单的数据,如 int、bool 等。我在我的一个项目中使用它。它的工作原理是这样的:当用户第一次启动应用程序时,应用程序检查SharedPreferences 中是否有loggedIn 数据。因为是第一次上线,所以没有数据。然后应用程序重定向到登录页面。用户登录他的帐户后,应用程序将loggedIn 数据保存到SharedPreferences。可以是bool、int等。用户关闭并再次打开应用后,应用会去查找loggedIn数据,然后重定向到用户页面。

以上是关于基于是新登录还是返回用户在 Flutter 中导航屏幕的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中使用 Firebase 身份验证检查用户是不是是新用户

如何在flutter中登录后禁用用户返回

如何在 Flutter 的底部导航中处理后退导航

Flutter/IOS混和开发情况下,项目内首个Flutter页面导航无返回问题

Flutter firebase 登录用户在登录后返回 NULL currentUser

Flutter 导航返回主屏幕