Flutter api
single instance
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:dio/dio.dart';
class TimerLoading {
static const int durationTime = 2;
static Timer _timer;
static TimerLoading _instance = TimerLoading._internal();
factory TimerLoading() => _instance;
TimerLoading._internal() {
// 初始化 变量 比如 timer
}
static TimerLoading getInstance() {
return TimerLoading();
}
showToast({String text}) {
EasyLoading.showToast(text);
Future.delayed(new Duration(seconds: durationTime),() {
//EasyLoading.dismiss();
});
}
}
factory Manager() =>_getInstance();
static Manager get instance => _getInstance();
static Manager _instance;
Manager._internal() {
}
static Manager _getInstance() {
if (_instance == null) {
_instance = new Manager._internal();
}
return _instance;
}
Manager manager = new Manager();
Manager manager2 = Manager.instance;
判断泛型数据类型
/// Response body. may have been transformed, please refer to [ResponseType].
T data;
enum ResponseType {
/// Transform the response data to JSON object only when the
/// content-type of response is "application/json" .
json,
/// Get the response stream without any transformation. The
/// Response data will be a `ResponseBody` instance.
///
/// Response<ResponseBody> rs = await Dio().get<ResponseBody>(
/// url,
/// options: Options(
/// responseType: ResponseType.stream,
/// ),
/// );
stream,
/// Transform the response data to a String encoded with UTF8.
plain,
/// Get original bytes, the type of [Response.data] will be List<int>
bytes
}
* response.data.runtimeType
json string to map
import 'dart:convert' as convert;
Map<String, dynamic> jsonData = convert.jsonDecode(response.data);
// 其中 response.data是json string
登陆:遇到token 失效 我们需要自己实现拦截器,在拦截器去刷新token,并重新进行网络请求
_dio.interceptors.add(DioLogInterceptor());
class DioLogInterceptor extends Interceptor {
@override
Future onError(DioError err) async {
// 获取本次网络请求的请求参数
params: err.response.request.queryParameters,
path: err.response.request.uri.path,
method: err.response.request.method
}
}
如果普通网络请求使用 Dio 单例,则需要使用dio的lock锁住单例,另外创建新的Dio对象去刷新token和retry.结束后再打开锁。注意异常后也要打开锁。
Dio put请求 以及参数注意事项
必须严格按照 Map<String, dynamic>类型 ,如果你是用Map()则会有意想不到的问题。
Future<bool> refreshToken() async {
Dio newDio = Dio();
newDio.options.baseUrl = Address.BASE_URL;
newDio.options.headers["token"] = Global.profile.getAuthorization;
Map refreshParams = Map<String, dynamic>();
refreshParams["refreshToken"] = Global.profile.refreshToken;
refreshParams["token"] = Global.profile.getAuthorization;
Response refreshTokenResponse;
try {
refreshTokenResponse = await newDio.request(Address.REFRESH_TOKEN,
queryParameters: refreshParams, options: Options(method: "PUT"));
} on DioError catch (e) {
}
return true;
}
json_serializable
flutter packages pub run build_runner build --delete-conflicting-outputs //删除并重新创建.g.dart文件
flutter packages pub run build_runner build
https://caijinglong.github.io/json2dart/index.html
convert.jsonDecode的崩溃是悄无声息的 使用之前务必做类型判断,因为你永远不知道服务端给你的response是Map还是String!!! 看心情
convert.jsonDecode
Map<String, dynamic> jsonData;
if (response is Map) {
jsonData = response.data;
} else {
jsonData = convert.jsonDecode(response.data);
}
更好用的json解析: flutter_json_format插件。选中文件 右键Generate -> flutter json format 自动生成。上面的那个json_serializable序列化方式总是有问题,不靠谱。
就像前面的String转Map 明明发生了异常 但是为何悄无声息? 因为Dart的异常会让接下来的函数的代码不执行 但是并不会影响整个程序的执行。
请求地址 直接拷贝到浏览器会有好效果
按钮
_buildBottomButton({int type, String title}) {
return Container(
child: Padding(
padding: type == 1 ? EdgeInsets.only(left: dimens.dimens_16,right: 8) : EdgeInsets.only(left: 8,right: dimens.dimens_16),
child: ButtonTheme(
// minWidth: 80,
// height: 44,
child: FlatButton(
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(8.0),
side: BorderSide(
color: type == 1 ? Colors.black12 : Theme
.of(context)
.accentColor)),
color: type == 1 ? Colors.black12 : Theme
.of(context)
.accentColor,
textColor: Colors.white,
padding: EdgeInsets.all(8.0),
onPressed: () {},
child: Text(
title,
style: TextStyle(
color: type == 1 ? Theme
.of(context)
.accentColor : Colors.white,
fontSize: 17,
),
),
),
),
),
);
}
HTTPS私有证书校验:Dio Image WebView三个场景都会遇到。外网则不会有这个问题。内网则会遇到。
import 'package:dio/adapter.dart';
httpsConfigure() {
/// 配置证书
/*
ByteData data =
await rootBundle.load('assets/certificate/xxx.crt');
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
SecurityContext securityContext = new SecurityContext();
securityContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
client = HttpClient(context: securityContext);
return client;
};
*/
/// 信任所有证书
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
return true;
};
};
}
webview 可以使用插件flutter_webview_plugin 参数有关于忽略私有证书的
上面是Dio的配置,如果是NetworkImage 加载一张https的图片,我们需要去看NetworkImage的源码 里面有类似介绍,但是在正式环境不可用。stackoverflow的大神的解决方法是把源码拷贝出来自己重新封装后使用!
Vague Search
// value 是search string
// widget.contacts是数据源
onSubmitted: (value) {
var result = widget.contacts
.where((element) => vagueSearch(element.name, value));
},
bool vagueSearch(String name, String searchString) {
return name.indexOf(searchString) > -1 ? true : false;
}
打包 命令行配置环境
flutter build ios --dart-define=SERVER_ENV=sit,API_HOST=https://xxx...
sit是对应SIT环境,可配置其他环境
API_HOST,SERVER_ENV参照文件
配合ENVConfig
GlobalKey的使用:性能优化
- 利用GolbalKey获取到对应Widget的State对象
- 创建继承自StatefulWidget的自定义Widget:MyWidget
- 构造函数带上Key 参数
- 创建MyWidget的实例的时候,先创建一个GlobalKey
对象(假设MyWidget对应的State类是MyWidgetState),,然后将key实例传入构造函数 - 在外界就可以获取MyWidget对应的State对象,也就是MyWidgetState实例。key.currentState.
- 可以在外部愉快的调用MyWidgetState实例的函数了,比如我们可以局部刷新MyWidget,来避免多余的性能开销。我们也借此实现无context的push操作。
- 新手往往会随意的调用setState,有时候明明只需要刷新一个小的Widget却刷新了整颗Widget。如果是频繁刷新,那么性能开销极其庞大。因此我们可以将这个小的Wideget单独抽取出来,利用上面讲的思路,只刷新必要的部分。
- 另一种局部刷新的思路:我们构建一个无限ListView,Item上一个按钮点击后,要改变这个Item的按钮状态(比如点赞),这个时候我们刷新整个ListView是不合适的,怎么做到只刷新当前Item? 甚至是只刷新当前按钮?
其实很简单,只刷新当前Item,我们只需要将Item单独抽取成一个StatefulWidget,让每个Item都有自己的State类,构造函数传入model对象,我们每次点击按钮,则仅仅改变一个model的某个属性值,一个model对应一个StatefulWidget,因此可以直接调用setState(){model.xx=xx}来只刷新item。
而站在item的角度,仅刷新按钮,我们可以采用前面介绍的GlobalKey来实现。
- 后面有时间专门写一个demo实现,这里是思路。
代码整理小技巧
属于本函数内部的重复代码,没必要在函数外新开一个函数进行封装,可以在函数内部做封装,非常便于阅读,避免了业务散落一地的感觉,参照下面代码的refensh函数。
Future<bool> _checkToken() async {
Function refresh = () async {
Log.v("need update token");
bool refreshTokenResult = await _refreshToken();
Log.v("update token result : $refreshTokenResult");
return refreshTokenResult;
};
/// check token
String token = Global.profile.getAuthorization;
if (null != token) {
var jwtRes;
try {
jwtRes = _parseJwt(token);
} catch (e) {
Log.v("parseJwt failed :$e");
return refresh();
}
if (null != jwtRes && null != jwtRes["exp"]) {
int time = jwtRes["exp"];
int now = TimeUtil.currentTimeMillis();
if (now / 1000 > time) {
return refresh();
}
}
}
return false;
}
上拉底部widget 只需将datasource.length+1
Widget _itemBuilder(BuildContext buildContext, List<dynamic> list, int index) {
if (list.length > index) {
return CompanyListItem(
data: list[index],
);
} else {
return Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'loading... ',
style: TextStyle(fontSize: 16.0),
),
CircularProgressIndicator()
],
),
),
);
}
}