基于是新登录还是返回用户在 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/IOS混和开发情况下,项目内首个Flutter页面导航无返回问题