B站视频

https://www.bilibili.com/vide...
https://www.bilibili.com/vide...
https://www.bilibili.com/vide...
https://www.bilibili.com/vide...
https://www.bilibili.com/vide...
https://www.bilibili.com/vide...

本节目标

  • 第一次登录显示欢迎界面
  • 离线登录
  • Provider 响应数据管理
  • 实现 APP 色彩灰度处理
  • 注销登录
  • Http Status 401 认证授权
  • 首页磁盘缓存
  • 首页缓存策略,延迟 1~3 秒
  • 首页骨架屏

视频

  • b 站
  • 油管镜像

资源

  • 蓝湖设计稿(加微信给授权 ducafecat)
    https://lanhuapp.com/url/wbhGq
  • YAPI 接口管理
    http://yapi.demo.qunar.com/
  • 代码
    https://github.com/ducafecat/...
  • 参考

    • provider
    • pk_skeleton

第一次显示欢迎界面、离线登录

  • lib/global.dart
  /// 是否第一次打开  static bool isFirstOpen = false;  /// 是否离线登录  static bool isOfflineLogin = false;  /// init  static Future init() async {    ...    // 读取设备第一次打开    isFirstOpen = !StorageUtil().getBool(STORAGE_DEVICE_ALREADY_OPEN_KEY);    if (isFirstOpen) {      StorageUtil().setBool(STORAGE_DEVICE_ALREADY_OPEN_KEY, true);    }    // 读取离线用户信息    var _profileJSON = StorageUtil().getJSON(STORAGE_USER_PROFILE_KEY);    if (_profileJSON != null) {      profile = UserLoginResponseEntity.fromJson(_profileJSON);      isOfflineLogin = true;    }
  • lib/pages/index/index.dart
class IndexPage extends StatefulWidget {  IndexPage({Key key}) : super(key: key);  @override  _IndexPageState createState() => _IndexPageState();}class _IndexPageState extends State<IndexPage> {  @override  Widget build(BuildContext context) {    ScreenUtil.init(      context,      width: 375,      height: 812 - 44 - 34,      allowFontScaling: true,    );    return Scaffold(      body: Global.isFirstOpen == true          ? WelcomePage()          : Global.isOfflineLogin == true ? ApplicationPage() : SignInPage(),    );  }}

Provider 实现动态灰度处理

https://pub.flutter-io.cn/pac...

步骤 1:安装依赖

dependencies:  provider: ^4.0.4

步骤 2:创建响应数据类

  • lib/common/provider/app.dart
import 'package:flutter/material.dart';/// 系统相应状态class AppState with ChangeNotifier {  bool _isGrayFilter;  get isGrayFilter => _isGrayFilter;  AppState({bool isGrayFilter = false}) {    this._isGrayFilter = isGrayFilter;  }}

步骤 3:初始响应数据

方式一:先创建数据对象,再挂载

  • lib/global.dart
  /// 应用状态  static AppState appState = AppState();
  • lib/main.dart
void main() => Global.init().then((e) => runApp(      MultiProvider(        providers: [          ChangeNotifierProvider<AppState>.value(            value: Global.appState,          ),        ],        child: MyApp(),      ),    ));

方式二:挂载时,创建对象

  • lib/main.dart
void main() => Global.init().then((e) => runApp(      MultiProvider(        providers: [          ChangeNotifierProvider<AppState>(            Create: (_) => new AppState(),          ),        ],        child: MyApp(),      ),    ));

步骤 4:通知数据发声变化

  • lib/common/provider/app.dart
class AppState with ChangeNotifier {  ...  // 切换灰色滤镜  switchGrayFilter() {    _isGrayFilter = !_isGrayFilter;    notifyListeners();  }}

步骤 5:收到数据发声变化

方式一:Consumer

  • lib/main.dart
void main() => Global.init().then((e) => runApp(      MultiProvider(        providers: [          ChangeNotifierProvider<AppState>.value(            value: Global.appState,          ),        ],        child: Consumer<AppState>(builder: (context, appState, _) {          if (appState.isGrayFilter) {            return ColorFiltered(              colorFilter: ColorFilter.mode(Colors.white, BlendMode.color),              child: MyApp(),            );          } else {            return MyApp();          }        }),      ),    ));

方式二:Provider.of

  • lib/pages/account/account.dart
    final appState = Provider.of<AppState>(context);    return Column(      children: <Widget>[        MaterialButton(          onPressed: () {            appState.switchGrayFilter();          },          child: Text('灰色切换 ${appState.isGrayFilter}'),        ),      ],    );

多个响应数据处理

  • 挂载用 MultiProvider
  • 接收用 Consumer2 ~ Consumer6

注销登录

  • lib/common/utils/authentication.dart
/// 检查是否有 tokenFuture<bool> isAuthenticated() async {  var profileJSON = StorageUtil().getJSON(STORAGE_USER_PROFILE_KEY);  return profileJSON != null ? true : false;}/// 删除缓存 tokenFuture deleteAuthentication() async {  await StorageUtil().remove(STORAGE_USER_PROFILE_KEY);  Global.profile = null;}/// 重新登录Future goLoginPage(BuildContext context) async {  await deleteAuthentication();  Navigator.pushNamedAndRemoveUntil(      context, "/sign-in", (Route<dynamic> route) => false);}
  • lib/pages/account/account.dart
class _AccountPageState extends State<AccountPage> {  @override  Widget build(BuildContext context) {    final appState = Provider.of<AppState>(context);    return Column(      children: <Widget>[        Text('用户: ${Global.profile.displayName}'),        Divider(),        MaterialButton(          onPressed: () {            goLoginPage(context);          },          child: Text('退出'),        ),      ],    );  }}

Http Status 401 认证授权

dio 封装界面的上下文对象 BuildContext context

  • lib/common/utils/http.dart
  Future post(    String path, {    @required BuildContext context,    dynamic params,    Options options,  }) async {    Options requestOptions = options ?? Options();    requestOptions = requestOptions.merge(extra: {      "context": context,    });    ...  }

错误处理 401 去登录界面

  • lib/common/utils/http.dart
    // 添加拦截器    dio.interceptors        .add(InterceptorsWrapper(onRequest: (RequestOptions options) {      return options; //continue    }, onResponse: (Response response) {      return response; // continue    }, onError: (DioError e) {      ErrorEntity eInfo = createErrorEntity(e);      // 错误提示      toastInfo(msg: eInfo.message);      // 错误交互处理      var context = e.request.extra["context"];      if (context != null) {        switch (eInfo.code) {          case 401: // 没有权限 重新登录            goLoginPage(context);            break;          default:        }      }      return eInfo;    }));

首页磁盘缓存

  • lib/common/utils/net_cache.dart
      // 策略 1 内存缓存优先,2 然后才是磁盘缓存      // 1 内存缓存      var ob = cache[key];      if (ob != null) {        //若缓存未过期,则返回缓存内容        if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <            CACHE_MAXAGE) {          return cache[key].response;        } else {          //若已过期则删除缓存,继续向服务器请求          cache.remove(key);        }      }      // 2 磁盘缓存      if (cacheDisk) {        var cacheData = StorageUtil().getJSON(key);        if (cacheData != null) {          return Response(            statusCode: 200,            data: cacheData,          );        }      }

首页缓存策略,延迟 1~3 秒

  • lib/pages/main/channels_widget.dart
  // 如果有磁盘缓存,延迟3秒拉取更新档案  _loadLatestWithDiskCache() {    if (CACHE_ENABLE == true) {      var cacheData = StorageUtil().getJSON(STORAGE_INDEX_NEWS_CACHE_KEY);      if (cacheData != null) {        Timer(Duration(seconds: 3), () {          _controller.callRefresh();        });      }    }  }

首页骨架屏

https://pub.flutter-io.cn/pac...

  • lib/pages/main/main.dart
  @override  Widget build(BuildContext context) {    return _newsPageList == null        ? cardListSkeleton()        : EasyRefresh(            enableControlFinishRefresh: true,            controller: _controller,            ...