阅读 324

flutter-登录token本地存储(shared_preferences)、路由拦截

登录token的处理,数据本地存储,路由拦截

flutter - 登陆界面&表单校验,登录后的处理

登录逻辑

添加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)

相关链接

flutter - 登陆界面&表单校验
flutter - 图文讲解表单组件基本使用 & 注册实战
SharedPreferences class

关注下面的标签,发现更多相似文章
评论