flutter-登录token本地存储(shared_preferences)、路由拦截
登录逻辑
添加token
- 登录成功,保存token到本地,转跳到首页,移除其他栈,防止返回回到登录页面
移除token
- 未登录 路由拦截找不到token,转跳到登录页面或者弹窗
- token过期,后台返回token,api拦截,移除token,转跳到登录页面
- 退出登录移除token,转跳到登录页面,移除前面所有路由栈
本地存储
flutter暂时没有内置本地存储,官方推荐shared_preferences
shared_preferences: ^0.5.4+1
复制代码
import 'package:shared_preferences/shared_preferences.dart';
复制代码
基本用法
实例下
final prefs = await SharedPreferences.getInstance();
复制代码
保存
保存字符类型数据setString
保存int类型数据setInt
设置布尔类型数据setBool
设置Double类型数据setDouble
获取
获取字符类型数据getString
获取int类型数据getInt
获取布尔类型数据getBool
获取Double类型数据getDouble
移除
移除数据remove
清空
清空所有数据clear
(更多更详细请看官方文档,最下面的相关链接里面的SharedPreferences class
)
登录成功数据存储(保存登录token)
Map<String, Object> data = response_map['data'];
final prefs = await SharedPreferences.getInstance();
final setTokenResult = await prefs.setString('user_token', data['token']);
await prefs.setInt('user_phone', data['phone']);
await prefs.setString('user_phone', data['name']);
if(setTokenResult){
debugPrint('保存登录token成功');
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => route == null,);
}else{
debugPrint('error, 保存登录token失败');
}
复制代码
没有登录转跳到登录页面
handleToken() async{
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('user_token') ?? '';
debugPrint('user_token: $token');
if(token == ''){
Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => route == null,);
}
}
复制代码
退出登录,移除本地存储
GestureDetector(
onTap: () async{
final prefs = await SharedPreferences.getInstance();
final result = await prefs.clear();
if(result){
debugPrint('退出登录成功');
Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => route == null,);
}
},
child: ListTile(
leading: const Icon(Icons.outlined_flag),
title: const Text('退出登录'),
),
)
复制代码
移除登录的按钮我是写在抽屉组件里面
flutter自带的drawer
组件
抽屉gif

Scaffold
组件有一个drawer
参数, 接收一个widget
Scaffold(
drawer: new MyDrawer(),
)
复制代码
MyDrawer
class MyDrawer extends StatelessWidget {
const MyDrawer({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(height: 100,),
Expanded(
child: ListView(
children: <Widget>[
GestureDetector(
onTap: () async{
final prefs = await SharedPreferences.getInstance();
final result = await prefs.clear();
if(result){
debugPrint('退出登录成功');
Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => route == null,);
}
},
child: ListTile(
leading: const Icon(Icons.outlined_flag),
title: const Text('退出登录'),
),
)
],
),
),
],
),
),
);
}
}
复制代码
退出登录可以二次确认下
AlertDialog(
content: Text('是否确认退出登录?'),
actions: <Widget>[
FlatButton(
child: Text('取消'),
onPressed: () {
debugPrint('取消');
Navigator.pop(context);
},
),
FlatButton(
child: Text('确认'),
onPressed: () async{
debugPrint('确认退出');
Navigator.pop(context);
final prefs = await SharedPreferences.getInstance();
final result = await prefs.clear();
if(result){
debugPrint('退出登录成功');
Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => route == null,);
}
},
),
],
);
复制代码
我们实现了登录,和退出,但是未登录还未处理,未登录需要路由拦截
路由拦截
onGenerateRoute
里面可以监听到路由(在routes和home匹配不到的时候才会执行)
- 把routes的代码删掉(如果有的话)
- 把home删掉(如果有的话)
MaterialApp(
...代码
// home: IndexHome(),
onGenerateRoute: onGenerateRoute,
// routes: routes,
);
复制代码
新建一个onGenerateRoute函数
Route<dynamic> onGenerateRoute(RouteSettings setting) {
if(setting.name == 路由名称){
return aterialPageRoute(builder: (context) => Widget);
}
return MaterialPageRoute(builder: (BuildContext context) => Container(child: Text('404'),));
}
复制代码
- 在onGenerateRoute函数里面加入如下代码,把routes写进入
Map<String, Widget> routes = {
你的路由
};
bool mathMap = false;
Route<dynamic> mathWidget;
routes.forEach((key, v){
if(key == setting.name){
mathMap = true;
mathWidget = MaterialPageRoute(builder: (BuildContext context) => v);
}
});
if(mathMap){
return mathWidget;
}
复制代码
这时候, 我们在里面加一个钩子管理就可以了
路由实现完整代码
Route<dynamic> onGenerateRoute(RouteSettings setting) {
routeHook(setting);
Map<String, Widget> routes = {
你的路由
};
bool mathMap = false;
Route<dynamic> mathWidget;
routes.forEach((key, v){
if(key == setting.name){
mathMap = true;
mathWidget = MaterialPageRoute(builder: (BuildContext context) => v);
}
});
if(mathMap){
return mathWidget;
}
return MaterialPageRoute(builder: (BuildContext context) => Container(child: Text('404'),));
}
复制代码
这时候我们需要在里面写钩子,这个就比较复杂了,我本来打开写一个beforeHook,接收一个next函数,然后突然发现,本地存储是异步的。这就很惆怅了,这时候只能用路由转跳返回到登录页面。
Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => route == null,);
复制代码
登录拦截实现
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
routeBeforeHook(RouteSettings setting, navigatorKey) async{
String LoginPath = '/login';
if(setting.name == LoginPath || setting.name == '/rigister'){
return;
}
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('user_token') ?? '';
if(token == ''){
navigatorKey.currentState.pushNamedAndRemoveUntil(LoginPath, (route) => route == null,);
}
}
复制代码
context全局化
Navigator需要依赖context
,这时候没context
或者context
在顶层,就会出问题,这时候可以使用全局的key。
在路由拦截和Api拦截中都可以用到
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
复制代码
MaterialApp下面的navigatorKey
navigatorKey: navigatorKey,
复制代码
context.currentState
等同于Navigator.of(context)
如转跳到登录页
context.currentState.pushNamedAndRemoveUntil(LoginPath, (route) => route == null,);
复制代码
其他
牛刀小试
功能写完了,可以牛刀小试了
@override
void initState() {
setUserData();
super.initState();
}
setUserData() async{
final prefs = await SharedPreferences.getInstance();
setState(() {
user_name = prefs.getString('user_name') ?? '';
phone = (prefs.getInt('user_phone') ?? 0).toString();
});
}
复制代码
意外小插曲
引入的时候出现了小插曲
Target of URI doesn't exist: 'package:shared_preferences/shared_preferences.dart'. Try creating the file referenced by the URI, or Try using a URI for a file that does exist.dart(uri_does_not_exist)
我从.packages
文件找到shared_preferences的目录,然后找到pubspec.yaml
,看了下name,是shared_preferences
,lib文件下也有shared_preferences
文件,这时候就奇怪了, 怎么会找不到呢。
(然后想不到问题,有可能是编辑器的问题,于是重启,就没事了QAQ)
作者:秀岳lonelyBoy
链接:https://juejin.im/post/5db6d6f16fb9a0206965a0f3
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。