跳到正文
Willh的个人博客 记录 · 思考 · 分享
《Flutter实战·第二版》

15.7 登录页

我们说过Github有多种登录方式,为了简单起见,我们只实现通过用户名和密码登录。在实现登录页时有四点需要注意:

  1. 可以自动填充上次登录的用户名(如果有)。
  2. 为了防止密码输入错误,密码框应该有开关可以看明文。
  3. 用户名或密码字段在调用登录接口前有本地合法性校验(比如不能为空)。
  4. 登录成功后需更新用户信息。

实现代码如下:

1import '../index.dart';
2
3class LoginRoute extends StatefulWidget {
4  
5  _LoginRouteState createState() => _LoginRouteState();
6}
7
8class _LoginRouteState extends State<LoginRoute> {
9  TextEditingController _unameController = TextEditingController();
10  TextEditingController _pwdController = TextEditingController();
11  bool pwdShow = false; //密码是否显示明文
12  GlobalKey _formKey = GlobalKey<FormState>();
13  bool _nameAutoFocus = true;
14
15  
16  void initState() {
17    // 自动填充上次登录的用户名,填充后将焦点定位到密码输入框
18    _unameController.text = Global.profile.lastLogin;
19    if (_unameController.text != null) {
20      _nameAutoFocus = false;
21    }
22    super.initState();
23  }
24
25  
26  Widget build(BuildContext context) {
27    var gm = GmLocalizations.of(context);
28    return Scaffold(
29      appBar: AppBar(title: Text(gm.login)),
30      body: Padding(
31        padding: const EdgeInsets.all(16.0),
32        child: Form(
33          key: _formKey,
34          autovalidate: true,
35          child: Column(
36            children: <Widget>[
37              TextFormField(
38                  autofocus: _nameAutoFocus,
39                  controller: _unameController,
40                  decoration: InputDecoration(
41                    labelText: gm.userName,
42                    hintText: gm.userNameOrEmail,
43                    prefixIcon: Icon(Icons.person),
44                  ),
45                  // 校验用户名(不能为空)
46                  validator: (v) {
47                    return v.trim().isNotEmpty ? null : gm.userNameRequired;
48                  }),
49              TextFormField(
50                controller: _pwdController,
51                autofocus: !_nameAutoFocus,
52                decoration: InputDecoration(
53                    labelText: gm.password,
54                    hintText: gm.password,
55                    prefixIcon: Icon(Icons.lock),
56                    suffixIcon: IconButton(
57                      icon: Icon(
58                          pwdShow ? Icons.visibility_off : Icons.visibility),
59                      onPressed: () {
60                        setState(() {
61                          pwdShow = !pwdShow;
62                        });
63                      },
64                    )),
65                obscureText: !pwdShow,
66                //校验密码(不能为空)
67                validator: (v) {
68                  return v.trim().isNotEmpty ? null : gm.passwordRequired;
69                },
70              ),
71              Padding(
72                padding: const EdgeInsets.only(top: 25),
73                child: ConstrainedBox(
74                  constraints: BoxConstraints.expand(height: 55.0),
75                  child: RaisedButton(
76                    color: Theme.of(context).primaryColor,
77                    onPressed: _onLogin,
78                    textColor: Colors.white,
79                    child: Text(gm.login),
80                  ),
81                ),
82              ),
83            ],
84          ),
85        ),
86      ),
87    );
88  }
89
90  void _onLogin() async {
91    // 提交前,先验证各个表单字段是否合法
92    if ((_formKey.currentState as FormState).validate()) {
93      showLoading(context);
94      User user;
95      try {
96        user = await Git(context).login(_unameController.text, _pwdController.text);
97        // 因为登录页返回后,首页会build,所以我们传false,更新user后不触发更新
98        Provider.of<UserModel>(context, listen: false).user = user;
99      } catch (e) {
100        //登录失败则提示
101        if (e.response?.statusCode == 401) {
102          showToast(GmLocalizations.of(context).userNameOrPasswordWrong);
103        } else {
104          showToast(e.toString());
105        }
106      } finally {
107        // 隐藏loading框
108        Navigator.of(context).pop();
109      }
110      if (user != null) {
111        // 返回
112        Navigator.of(context).pop();
113      }
114    }
115  }
116}

代码很简单,关键地方都有注释,不再赘述,下面我们看一下运行效果,如图15-5所示。

图15-5