前端的flutter之路(二):项目前期准备

4,765 阅读3分钟

最近时间比较多,就把项目中可能用到的知识用 flutter 大概写了一遍,所以就分享出来增强下印象。

1. 路由库:fluro

安装

官方文档直接使用版本号的这个部分说错了,版本号不需要带引号的,所以正确的方式是在 pubspec.yaml文件中的dependencies的下方加入

fluro: ^1.5.1

之后编辑器即可根据 flutter 命令自行安装依赖,或者在项目根目录运行 flutter pub get,具体流程见 flutter 官网

使用

官方文档说的比较简略,我看了 flutter go 的代码以后,总结了这个路由库的使用方式如下:

  1. 定义一个类,实现一个方法 对传入的 Router 实例进行扩展。代码在这里
  2. 确保执行代码的 一开始 进行调用上述的类方法进行路由的初始化,比如 runApp 方法运行之前或者在 根类 MyApp 的构造函数 中,我选择了后者。router 实例最好是全局变量,这样在其他文件中也能访问代码在这里
  3. MaterialApponGenerateRoute 改写为以下代码。在这里用到了 router,所以一定要在第 2 步中确保路由已经被正确初始化。
onGenerateRoute: (settings) {
  return Application.router.generator(settings);
}
  1. 在页面中进行跳转的时候,可以使用该库所提供的方法,router.navigateTo(context, "/users/1234", transition: TransitionType.fadeIn);,也可以使用 flutter 提供的路由跳转方式,Navigator.of(context).pushNamed("/users/1234", arguments: {"from": "ui"})

需要修改页面转场动画使用前者,需要携带额外的参数建议使用后者。因为转场动画可以在第 2 步中进行设置,并且前者只提供了动态参数的传递,如果需要传递 Map数据还得编码解码,所以我更人更推荐后者。

2. webviewk 库: webview_flutter

安装

webview_flutter: ^0.3.16

使用

代码在这里

  1. 在目录 ios/Runner/Info.plistdict内添加 <key>io.flutter.embedded_views_preview</key> <string>YES</string>
  2. 在文件内引入包之后就可以直接使用 WebView了,使用方式相对于插件的方式要简单的多,在定义组件时可以提供方法供 js 使用,向 js 传值则需要控制器的 evaluateJavascript 方法。
WebView(
  // webview的url
  initialUrl: url ?? 'https://www.baidu.com/',
  // 监听页面url的变化,根据返回值决定跳转还是阻止
  navigationDelegate: (request) {
    if (request.url.startsWith('https://www.youtube.com/')) {
      print('blocking navigation to $request}');
      return NavigationDecision.prevent;
    }

    return NavigationDecision.navigate;
  },
  // flutter向js注入全局变量,然后js就可以调用flutter的方法
  // 例子中js的window会多出一个 Print 对象,js调用Print.postMessage('我是js')时,在flutter中则会打印 js传过来的值: 我是js
  javascriptChannels: {
    JavascriptChannel(
      name: 'Print',
      onMessageReceived: (JavascriptMessage msg) {
        print('js传过来的值: ' + msg.message);
      }
    ),
  },
  // 是否允许运行js
  javascriptMode: JavascriptMode.unrestricted,
  // webview 创建后肤将控制器赋值给实例变量,方便控制
  onWebViewCreated: (webViewController) {
    // _controller.complete(webViewController);
    _webViewController = webViewController;
  },
  // 页面加载完成
  onPageFinished: (String msg) {
    // print('加载完成');
  },
)
  1. 在后退时,默认使用的是 flutter 的后退方式,如果需要在 webview 内进行回退,则需要在外面包一层 WillPopScope来监听页面的返回,从而进行更改。
Future<bool> back() async {
  bool canBack = await _webViewController?.canGoBack();
  if (canBack) {
    _webViewController.goBack();
  } else {
    Navigator.of(context).pop();
  }

  return false;
}
  1. 使用控制器的 evaluateJavascript可以通过 flutter 执行 js,从而得到 js 返回的值或是向 js 中注入变量(比如用户信息等),但这是有限制的。在安卓,执行后返回的值是 JSON 格式化后的字符串。而在 iOS 中,只有基本类型的字符串,数组类型的字符串可以被返回,其他非基本类型则不被支持并且该Future 会以 error 形式返回。

3. HTTP 请求库:dio

安装

dio: ^3.0.1

使用

dio 的使用方式跟前端平时使用的 axios 区别不是很大,所以我在这里就只说一下我之前遇到的困难。

  1. dio 的 interceptors 问题。其实这个主要还是 dart 学习的不好,在函数或者方法以外,只能声明变量,但是不能执行实例的方法,所以下述的代码会报错。
Dio dio = new Dio();
dio.interceptors.add(DioCacheManager(CacheConfig(baseUrl: baseUrl)).interceptor);

所以要封装某一个类 A 的时候,得新建一个类 B,然后在 B 的某一个方法中执行 A 的方法,进行操作后再进行返回。代码在这里

  1. 在进行 http 请求时,根据参数来选择是否显示 loading 框。看了文档以后,发现 extra 参数在请求和响应中都存在,但是文档中并没有提示 extra 是怎么传入的。后来看了类型提示,发现 extra 参数是包含在请求参数的 options 中的。其实,没有必要通过 extra 传递 loading 这个参数,可以直接在请求的地方进行封装。关于 loading 框的代码在这里

  2. 处理响应数据,根据后端的错误码进行相应处理。本来想在 interceptors 中对响应数据进行处理的,但是如果这么处理之后,返回数据的类型提示就不正确了,所以最后还是在拿到响应数据之后才进行处理的。

4. json 转 dart 类:json_model

安装

dependencies:
  json_annotation: ^2.0.0
dev_dependencies:
  json_model: ^0.0.2
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

使用

  1. 项目根目录下新建 jsons 文件夹
  2. 在该文件夹下新建 json 文件
  3. 执行 flutter packages pub run json_model

5. http 请求缓存:dio-http-cache

安装

dependencies:
  dio_http_cache: ^0.2.0

dio-http-cache 的 0.2.0 版本与 json_model 库依赖冲突,为解决依赖冲突依赖冲突

json_model的依赖目前是
  json_annotation: ^2.2.0
  json_serializable: ^2.0.0

为了兼容http_cache,需要升级这两个依赖包的版本
  json_annotation: ^3.0.0
  json_serializable: ^3.0.0

使用

  1. 在dio中添加 dio-http-cache interceptor
dio.interceptors.add(DioCacheManager(CacheConfig(baseUrl: "http://www.google.com")).interceptor);
  1. 在请求options中添加
options: buildCacheOptions(
  Duration(days: 7),
  forceRefresh: true,
  options: Options(
    headers: headers,
    method: method,
    extra: {
      'needLoading': needLoading ?? true,
      ...(extra ?? {}),
    },
  ),
)

6. toast 库:bot_toast

为什么 toast 还需要使用单独的库呢?因为在 flutter 中,要显示 toast 的话,一般都需要使用到 BuildContext context,而使用这个库的话,只需要用 BotToastInit 控件包裹一次,之后就可以直接使用了;否则就需要建立一个全局变量的 context(不知道是否可行)或是每次都传入 context。

flutter的initState 声明周期中,可以得到 context,但无法真正使用它,因为框架还没有完全将其与 state 关联。直到 initState() 方法执行完成,State 对象就被初始化并且 context 变为可用。所以在 initState 方法中进行 http 请求时,不能直接显示 toast。

// 第一帧 build 结束时触发,此时context可用
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((callback){

  });
}

安装

dependencies:
  bot_toast: ^2.1.0

使用

  1. 导入 BotToast 库
import 'package:bot_toast/bot_toast.dart';
  1. 初始化 BotToast
// 1.使用BotToastInit直接包裹MaterialApp
BotToastInit(
  child: MaterialApp(
    title: 'BotToast Demo',
    // 2.注册路由观察者
    navigatorObservers: [BotToastNavigatorObserver()],
    home: XxxxPage(),
  )
);
  1. 使用 BotToast
BotToast.showText(text:"xxxx");  //弹出一个文本框;
BotToast.showSimpleNotification(title: "init"); //弹出简单通知Toast
BotToast.showLoading(); //弹出一个加载动画

7. 上拉加载下拉刷新:flutter_easyrefresh

安装

dependencies:
  flutter_easyrefresh: ^2.0.4

使用

代码在这里

不得不说,这个库真是相当的方便,比 web 端写起来要简单的多了。官方文档写的已经很详细了,我就不写了。

8. 轮播图:flutter_swiper

安装

dependencies:
  flutter_swiper: ^1.1.6

使用

代码在这里

这个库也是很强大了,基本上常用的功能都有。

如果要实现 tab 与 swiper 联动的话,

// swiper onIndexChanged
Swiper(
  itemCount: 2,
  itemBuilder: (context, index) {
    return Container(
      color: Color(0xFF82B1FF),
      child: Center(child: Text('$index',)),
    );
  },
  onIndexChanged: (index) {
    _tabController.index = index;
  },
  controller: _swiperController,
)

// tab onTap
TabBar(
  tabs: tabs,
  labelPadding: EdgeInsets.only(bottom: 12.0),
  indicatorSize: TabBarIndicatorSize.label,
  indicatorWeight: 3.0,
  onTap: (int index) {
    _swiperController.move(index);
  },
  controller: _tabController,
)

9. 状态管理:Event Bus

flutter 的状态管理库还是挺多的,比如官方推荐的 Provider,还有 redux,mobx,bloc,rxdart 等,主要是目前的项目还很小,需要状态共享的地方不多,我就用 event bus 先凑合了。

安装

dependencies:
  event_bus: 1.1.0

使用

  1. 创建一个实例
import 'package:event_bus/event_bus.dart';

EventBus eventBus = EventBus();
  1. 定义事件类
class PieceEvent {
  bool isBlack;
  PieceEvent({ this.isBlack = true });
}
  1. 监听该事件
eventBus.on<PieceEvent>().listen((event) {
  setState(() {
    isBlack = event.isBlack;
  });
});
  1. 触发该事件
eventBus.fire(PieceEvent(isBlack: isBlack));

10. 其他

  1. [译] Flutter 核心概念详解: Widget、State、Context 及 InheritedWidget
  2. Stateful Widget Lifecycle
  3. material icon
  4. emoji

代码地址: github.com/ma125120/fl…