flutter-登陸token本地存儲(shared_preferences)、路由攔截

登陸token的處理,數據本地存儲,路由攔截html

flutter - 登錄界面&表單校驗,登陸後的處理api

登陸邏輯

添加token

  • 登陸成功,保存token到本地,轉跳到首頁,移除其餘棧,防止返回回到登陸頁面

移除token

  • 未登陸 路由攔截找不到token,轉跳到登陸頁面或者彈窗
  • token過時,後臺返回token,api攔截,移除token,轉跳到登陸頁面
  • 退出登陸移除token,轉跳到登陸頁面,移除前面全部路由棧

本地存儲

flutter暫時沒有內置本地存儲,官方推薦shared_preferencesbash

shared_preferences: ^0.5.4+1
複製代碼
import 'package:shared_preferences/shared_preferences.dart';
複製代碼

基本用法

實例下less

final prefs = await SharedPreferences.getInstance();
複製代碼
保存

保存字符類型數據setString
保存int類型數據setInt
設置布爾類型數據setBool
設置Double類型數據setDouble異步

獲取

獲取字符類型數據getString
獲取int類型數據getInt
獲取布爾類型數據getBool
獲取Double類型數據getDoubleasync

移除

移除數據remove編輯器

清空

清空全部數據clearide

(更多更詳細請看官方文檔,最下面的相關連接裏面的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('退出登陸'),
    ),
 )
複製代碼

移除登陸的按鈕我是寫在抽屜組件裏面post

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

相關文章
相關標籤/搜索