Flutter构建带缓存机制的全局用户登录信息架构

继前篇flutter初探之后,此次我来聊聊从传统web项目角度剖析我是怎么一步步搭建flutter基础项目的。本文主要讲述如何实现flutter用户数据多页面共享,用户数据本地缓存。

image.png

首先,我们引入数据共享组件Provider https://flutter.cn/docs/development/data-and-backend/state-mgmt/intro
我们用到的核心模块有:
1.ChangeNotifier ——共享数据源头部(数据存储、数据传播)
2.ChangeNotifierProvider ——共享数据视图层注入组件
3.Consumer ——共享数据作用节点
我们用到的核心方法有:
1.Provider.of<Model>(context, listen: true).Fuc

下面开始实现userInfo数据共享
第一部分:构建数据源,也就是基于ChangeNotifier的UserInfo类

import 'package:flutter/material.dart'; //ChangeNotifier需要material素材库
import 'package:flutter_app2/models/UserModel.dart'; //这个是根据接口数据构建的数据解析类
class UserInfo extends ChangeNotifier{
  UserModel _info;
  UserModel get info => _info ?? null;
  void setInfo(info){
    _info = UserModel.fromJson(info);
    notifyListeners();
  }
}

利用https://javiercbk.github.io/json_to_dart/在线工具可快速将后端返回的JSON数据转化成Dart JSON类

image.png

你可能会问为啥要转类型,我一开始觉得没必要,可是当我使用返回的数据,直接拿access_token参数的时候,就报错了~转换一下,可以规避这种特殊字符报错问题。

class UserModel {
  String name;
  String accessToken;
  String mobile;

  UserModel({this.name, this.accessToken, this.mobile});

  UserModel.fromJson(Map<String, dynamic> json) {
    name = json['name'];
    accessToken = json['access_token'];
    mobile = json['mobile'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['name'] = this.name;
    data['access_token'] = this.accessToken;
    data['mobile'] = this.mobile;
    return data;
  }
}

第二部分:用数据注入组件包裹这个项目入口

void main() => runApp(
    ChangeNotifierProvider(
      builder: (context) => UserInfo(),
      child: MyApp(),
    )
);

第三部分:实现视图结构显示

Consumer<UserInfo>(
                    builder: (context,user,child){
                        return Text(
                            user.info != null ? user.info.name : "去登录"
                        );
                    }
                  )

第四部分:设置数据

Provider.of<UserInfo>(context, listen: true).setInfo(mapUserInfo);

完整主页代码_main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; //布局适配库
import 'package:provider/provider.dart'; //Provider依赖
import 'providers/UserInfo.dart'; //UserInfo全局数据源
import 'package:flutter_app2/pages/login.dart'; //登录页面
import 'package:shared_preferences/shared_preferences.dart'; //缓存依赖
import 'dart:convert' as Convert; //数据转化库
void main() => runApp(
    ChangeNotifierProvider( //将数据注入器加载最外部节点,代表UserInfo将贯穿整个项目
      builder: (context) => UserInfo(), //注入UserInfo数据
      child: MyApp(),
    )
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePage createState()=>_HomePage();
}

class _HomePage extends State<HomePage> {
  @override
  initState() {
    super.initState();
    getUserInfo(); //初始化项目,获取本地缓存的用户数据
  }
  getUserInfo() async {
    var prefs = await SharedPreferences.getInstance();
    var userInfo = prefs.getString("userInfo");
    var mapUserInfo = Convert.jsonDecode(userInfo); //转化成Map<String Dynamic>类型
    Provider.of<UserInfo>(context, listen: true).setInfo(mapUserInfo);  //设置全局用户信息
    //写到这里我发现我好想没有处理找不到本地用户数据的情况,后续改正
  }
  Widget build(BuildContext context) {
    ScreenUtil.init(context, width: 750, height: 1334); //初始化布局适配参数
    return Scaffold(
      appBar: AppBar( title: Text("用户体系"),),
      body: Container(
        child: ListView(
          children: <Widget>[
            Container(
                child:Text("首页")
            ),
            Center(
              child:Container(
                width: ScreenUtil().setWidth(200),
                height: ScreenUtil().setHeight(200),
                child: RaisedButton(
                  onPressed: ()  {
                    Navigator.push(context, MaterialPageRoute(builder: (context) { //跳转至登录页
                      return Login(); 
                    }));
                  },
                  child: Consumer<UserInfo>(
                    builder: (context,user,child){
                        return Text(
                            user.info != null ? user.info.name : "去登录" //如果用用户信息显示名字,如果没有显示去登录
                        );
                    }
                  )
                ),
              )
            )
          ],
        ),
      ),
    );
  }
}

完整登录页代码_Login.dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_app2/common/Request.dart';
import 'package:flutter_app2/providers/UserInfo.dart';
import 'package:flutter_app2/models/UserModel.dart';
import 'dart:convert' as Convert;
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_app2/widgets/LoadingDialog.dart';
class Login extends StatelessWidget{
 @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar:AppBar( title: Text("登录"), ),
      body: Container(
        child: RaisedButton(
          onPressed: () async {
            Navigator.of(context).push(MaterialPageRoute(builder: (context){ //请求数据打开加载提示框,这里我随便拿了别人的LoadingDialog
              return LoadingDialog();
            }));
            var responce = await Request().getInstance().get("http://yujun.store/userInfo.php");//请求用户数据,这里用到了await跟js的await一样,外面的方法体需要用async修饰(本人也会php跟node服务端语言)
            Map<String, dynamic> userInfo = Convert.jsonDecode(responce.toString());//转化数据,我自己写的用户数据是标准的JSON,但是还是需要toString之后在jsonDecode
            Provider.of<UserInfo>(context, listen: true).setInfo(userInfo);
            var prefs = await SharedPreferences.getInstance(); //存一遍本地
            prefs.setString("userInfo", Convert.jsonEncode(userInfo)); //存Sring            Navigator.of(context).pop(context);//关闭加载提示框
          },
          child:  Consumer<UserInfo>(
              builder: (context,user,child){
                return Text(
                    user.info != null ? user.info.name : "去登录"
                );
              }
          ),
        ),
      ),
    );
  }
}

完整的请求Dio封装代码_Request.dart

import 'dart:io';
import 'package:dio/dio.dart';
import 'dart:convert' as Convert;
import 'package:shared_preferences/shared_preferences.dart';
import '../models/auth.dart';
class Request {
  Request _instance;
  Dio dio = new Dio();
  getInstance(){
    if(null == _instance){
      _instance = new Request();
      initial();
    }
    return _instance;
  }
  initial()async{
    dio.options.connectTimeout=10000;
    dio.options.receiveTimeout=10000;
    dio.options.responseType=ResponseType.JSON;
  }
  postFormData(String url, data) async{ //formData数据提交
   dio.options.contentType = ContentType.parse("application/x-www-form-urlencoded");
   var response = await post(url,data);
   return response;
  }
  get(String url) async{
    var response = await request('get',url,null);
    return response;
  }
  post(String url, data) async{
    var response = await request('post',url,data);
    return response;
  }
  request(String type,String  url, data) async{
    Response response;
    try{
      if(type == 'get'){
        response = await dio.get(url);
      }else{
        response = await dio.post(url,data: data??{});
      }
      return response;
    }on DioError catch(error){
      print(error);
    }
  }
}

完整的加载提示框代码_LoadingDialog.dart

import 'package:flutter/material.dart';
class LoadingDialog extends Dialog {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: new Material(
        ///背景透明
        color: Colors.transparent,
        ///保证控件居中效果
        child: new Center(
          ///弹框大小
          child: new SizedBox(
            width: 120.0,
            height: 120.0,
            child: new Container(
              ///弹框背景和圆角
              decoration: ShapeDecoration(
                color: Color(0xffffffff),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(8.0),
                  ),
                ),
              ),
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  new CircularProgressIndicator(),
                  new Padding(
                    padding: const EdgeInsets.only(
                      top: 20.0,
                    ),
                    child: new Text(
                      "加载中",
                      style: new TextStyle(fontSize: 16.0),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

providers下的UserInfo.dart

import 'package:flutter/material.dart';
import 'package:flutter_app2/models/UserModel.dart';
class UserInfo extends ChangeNotifier{
  UserModel _info;
  UserModel get info => _info ?? null;
  void setInfo(info){
    _info = UserModel.fromJson(info);
    notifyListeners();
  }
}

我的代码组织如下:


image.png

项目依赖如下:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  shared_preferences: ^0.5.4+8
  dio: ^1.0.9
  provider : ^3.2.0
  flutter_screenutil: ^1.0.1

这样就实现了一个有缓存功能的全局用户数据共享项目架构,谢谢阅读。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,682评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,277评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,083评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,763评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,785评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,624评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,358评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,261评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,722评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,030评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,737评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,360评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,941评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,057评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,237评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,976评论 2 355

推荐阅读更多精彩内容