本文最后更新于 1174 天前,其中的信息可能已经有所发展或是发生改变。
1. 页面基类
支持网络断开后重试、仿Android生命周期
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_basic/flutter_biz_widgets.dart';
import 'package:flutter_basic/flutter_http_dio.dart';
import 'package:connectivity/connectivity.dart';
import 'package:flutter_basic/flutter_utils.dart';
abstract class BasePage extends StatefulWidget {
const BasePage({ Key key }) : super(key: key);
@override
BasePageState createState() => getState();
///子类实现
BasePageState getState();
}
abstract class BasePageState<T extends BasePage> extends State<T> with RouteAware,WidgetsBindingObserver{
CancelToken cancelToken;
// 记录当前网络状态 如:当前网络未链接,一定时间后,网络恢复后需要拉起重新链接 此为标识符
String networkStatus;
final Connectivity _connectivity = Connectivity();
StreamSubscription<ConnectivityResult> _connectivitySubscription;
// 优化
AppLifecycleState lastState;
bool isPause = false;
//网络初始状态
netListener() {
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen((ConnectivityResult result) {
if(networkStatus==ConnectivityResult.none.toString() && result!=ConnectivityResult.none){
retry();
}
networkStatus = result.toString();
});
}
// 判断网络是否可用
bool isNetValid(){
return networkStatus==ConnectivityResult.none.toString();
}
Future<bool> checkNetConnect() async {
var connectivityResult = await (_connectivity.checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
return true;
}
return false;
}
//网络结束监听
connectivityDispose() {
_connectivitySubscription.cancel();
}
//网络进行监听
Future<Null> initConnectivity() async {
//平台消息可能会失败,因此我们使用Try/Catch PlatformException。
try {
networkStatus = (await _connectivity.checkConnectivity()).toString();
netListener();
} on PlatformException catch (e) {
print(e.toString());
networkStatus = 'Failed to get connectivity.';
}
}
@override
void initState() {
initConnectivity();
cancelToken = CancelToken();
onCreate();
super.initState();
}
@override
void dispose() {
cancelToken?.cancel("页面销毁,网络请求自动关闭!");
connectivityDispose();
Future.delayed(Duration(milliseconds: 300));
onDestroy();
super.dispose();
IIRouteObserver.instance.unsubscribe(this);
WidgetsBinding.instance.removeObserver(this);
}
@override
Widget build(BuildContext context) {
}
// 网络从不可用到可用时 进行回调 可以用于刷新数据
void retry(){}
@override
void didPushNext() {
isPause = true;
onPause();
}
@override
void didPop() {
onStop();
}
@override
void didPopNext() {
isPause = false;
onReStart();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// 前台到后台 inactive->paused
// 后台到前台 resumed
if(lastState!=state && !isPause){
lastState = state;
if (state == AppLifecycleState.resumed) {
onReStart();// onResume(); 也行 onReStart比较合适 因为先进入后台在回到前台 那么这个是 “再”的感觉, 初始化进入 再上面以及执行了 onResume
} else if (state == AppLifecycleState.inactive) {// 处于非激活状态,无法响应用户输入
onPause();
} else if (state == AppLifecycleState.paused) { // 不可见且无法响应用户输入
onStop();
} else if (state == AppLifecycleState.detached) {
onDestroy();
}
}
}
void onCreate() {}
void onDestroy() {
FTShowToastUtils.dismiss();
}
void onResume() {}
void onPause() {}
void onStop() {}
void onReStart() {}
@override
void didChangeDependencies() {
super.didChangeDependencies();
WidgetsBinding.instance.addObserver(this);
IIRouteObserver.instance.subscribe(this, ModalRoute.of(context));
}
}
2. 对话框Dialog基类
将间距、圆角等封装
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
abstract class BaseDialog extends StatefulWidget {
const BaseDialog({Key? key}) : super(key: key);
@override
BaseDialogState createState() => getState();
///子类实现
BaseDialogState getState();
}
abstract class BaseDialogState<T extends BaseDialog> extends State<T> {
int milliseconds = 120;
double width = 0.8.sw;
double radis = 10;
double paddingHorizontal = 12.w;
double paddingVertical = 12.h;
@override
void initState() {
super.initState();
setDuration(milliseconds);
setDialogWidth(width);
setDialogRadis(radis);
setDialogPdHorizontal(paddingHorizontal);
setDialogPdVertical(paddingVertical);
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedContainer(
width: 0.8.sw,
duration: Duration(milliseconds: milliseconds),
curve: Curves.easeInCubic,
child: Material(
borderRadius: BorderRadius.circular(10),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
child: body(),
),
),
),
);
}
// dialog主页面
Widget body();
void setDuration(int milliseconds) {
this.milliseconds = milliseconds;
}
void setDialogWidth(double width) {
this.width = width;
}
void setDialogRadis(double radis) {
this.radis = radis;
}
void setDialogPdHorizontal(double paddingHorizontal) {
this.paddingHorizontal = paddingHorizontal;
}
void setDialogPdVertical(double paddingVertical) {
this.paddingVertical = paddingVertical;
}
}
3. GetX控制器基类封装
3.1 Base
// 用于设置dio的CancelToken
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
import 'package:huadian/net/network_manager.dart';
import 'package:huadian/net/result.dart';
import 'package:huadian/tools/log_util.dart';
class BaseGetXController<T> extends GetxController {
fin// 用于设置dio的CancelToken
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
import 'package:huadian/ext/extends.dart';
import 'package:huadian/net/network_manager.dart';
import 'package:huadian/net/result.dart';
import 'package:huadian/route/route_config.dart';
import 'package:huadian/tools/alert_toast.dart';
import 'package:huadian/tools/config.dart';
import 'package:huadian/util/hjt_utils.dart';
enum ViewErrorStateType{
ERROR_CLIENT,
ERROR_SERVICE,
ERROR_TIMEOUT,
ERROR_NETWORK,
ERROR_PARAMS,
DEFAULT
}
enum ViewState{
IDLE,// 空闲中
BUSY,
EMPTY,
ERROR,
// 仅用于封装内部标记 调用者无需关心干嘛
SUCCESS
}
abstract class BaseGetXController<T> extends GetxController {
var _errorStateType = ViewErrorStateType.DEFAULT.obs;
var _viewState = ViewState.IDLE.obs;
final sfs = GetStorage();
CancelToken _cancelToken = CancelToken();
T get to => Get.find();
CancelToken get cancelToken => _cancelToken;
@override
void onClose() {
super.onClose();
_cancelToken.cancel("关闭页面,取消正在的网络请求!");
}
Future<Result?> get(String url, Map param,
{bool needToken = true,bool showLoading = true}) async {
_viewState.value = ViewState.BUSY;
Result? response = await HjtDio()
.get(url, param, token: needToken, cancelToken: _cancelToken,showLoading: showLoading);
_viewState.value = ViewState.IDLE;
return handleResponse(response);
}
Future<Result?> post<T>(String url, Map param,
{bool needToken = true,bool showLoading = true}) async {
_viewState.value = ViewState.BUSY;
Result? response = await HjtDio()
.post<T>(url, param, token: needToken, cancelToken: _cancelToken,showLoading: showLoading);
_viewState.value = ViewState.IDLE;
return handleResponse(response);
}
Result? handleResponse(Result? response){
Result? result;
response?.run((){
switch(response.code){
case TOKEN_EXPIRE:
loginExpire();
break;
case ERROR_TIMEOUT:
_errorStateType.value = ViewErrorStateType.ERROR_TIMEOUT;
_viewState.value = ViewState.ERROR;
break;
case ERROR_SERVICE:
_errorStateType.value = ViewErrorStateType.ERROR_SERVICE;
_viewState.value = ViewState.ERROR;
break;
case ERROR_CLIENT:
_errorStateType.value = ViewErrorStateType.ERROR_CLIENT;
_viewState.value = ViewState.ERROR;
break;
case ERROR_PARAMS:
_errorStateType.value = ViewErrorStateType.ERROR_PARAMS;
_viewState.value = ViewState.ERROR;
if(response.data is String && !(response.data as String).isNullOrEmpty())
EasyLoading.showError("${response.data}");
else
EasyLoading.showError("${response.msg}");
break;
case ERROR_NETWORK:
_errorStateType.value = ViewErrorStateType.ERROR_NETWORK;
_viewState.value = ViewState.ERROR;
break;
case SUCCESS:
// 需要 调用者 对数据判断 为"空" 设置状态
_errorStateType.value = ViewErrorStateType.DEFAULT;
_viewState.value = ViewState.SUCCESS;
result = response;
break;
}
});
return result;
}
// 上传单个图片
uploadSingleImage() async {
await HjtUtils.uploadSingleImage(cancelToken: _cancelToken);
}
// 上传多个多拍
uploadMultiImage() async {
await HjtUtils.uploadMultiImage(cancelToken: _cancelToken);
}
void loginExpire() {
AlertToast().showErrorAlertToast("登录状态失效!请重新登录。");
Future.delayed(Duration(seconds: 1),() {
Get.toNamed(RouteConfig.login);
});
}
bool get isEmpty => _viewState.value == ViewState.EMPTY;
bool get isIdle => _viewState.value == ViewState.IDLE;
bool get isBusy => _viewState.value == ViewState.BUSY;
bool get isError => _viewState.value == ViewState.ERROR;
bool get isErrorClient => _errorStateType.value == ViewErrorStateType.ERROR_CLIENT;
bool get isErrorService => _errorStateType.value == ViewErrorStateType.ERROR_SERVICE;
bool get isErrorTimeout => _errorStateType.value == ViewErrorStateType.ERROR_TIMEOUT;
bool get isErrorNetwork => _errorStateType.value == ViewErrorStateType.ERROR_NETWORK;
bool get isErrorParams => _errorStateType.value == ViewErrorStateType.ERROR_PARAMS;
bool get isErrorDefault => _errorStateType.value == ViewErrorStateType.DEFAULT;
void setEmpty() {
_viewState.value = ViewState.EMPTY;
}
get viewState => _viewState.value;
get errorStateType => _errorStateType.value;
}
3.2 BasePageController继承上诉的用于分页使用
// 用于设置dio的CancelToken
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
import 'package:huadian/base/base_getx_controller.dart';
import 'package:huadian/net/network_manager.dart';
import 'package:huadian/net/result.dart';
import 'package:huadian/tools/log_util.dart';
import 'package:huadian/util/hjt_utils.dart';
abstract class BasePageGetXController<T> extends BaseGetXController<T> {
// 多 Tab使用 一个Controller,保存每个tab页面的当前分页 page变量。切换tab重置 page
int _page = 1;
// 防止 页码参数不是这个 外部设置 一般都是这个除非 xxx
String _pageParams = "page";
String get pageParams => _pageParams;
set pageParams(String value) {
_pageParams = value;
}
int get page => _page;
set page(int value) {
_page = value;
}
// 一些控制器等
EasyRefreshController _easyRefreshController = EasyRefreshController();
ScrollController _scrollController = ScrollController();
EasyRefreshController get easyRefreshController => _easyRefreshController;
ScrollController get scrollController => _scrollController;
Future<Result?> get(String url, Map param,
{bool needToken = true,bool loadMore = false,bool showLoading = true}) async {
return await super.get(url, handleLoadMore(param,loadMore), needToken: needToken,showLoading: showLoading);
}
Future<Result?> post<T>(String url, Map param,
{bool needToken = true,bool loadMore = false,bool showLoading = true}) async {
return await super.post<T>(url, handleLoadMore(param,loadMore), needToken: needToken,showLoading: showLoading);
}
Map handleLoadMore(Map param, bool loadMore) {
if(loadMore){
page = page + 1;
param[pageParams] = page;
}else{
page = 1;
param[pageParams] = 1;
}
return param;
}
// 控制器的状态
void finishRefresh() {
resetLoadState();
easyRefreshController.finishRefresh();
}
void resetLoadState(){
easyRefreshController.resetLoadState();
}
void finishLoad(bool noMore){
easyRefreshController.finishLoad(noMore: noMore);
}
}
4. 网络层
import 'dart:convert';
import 'dart:typed_data';
import 'package:connectivity/connectivity.dart';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http_parser/http_parser.dart';
import 'dart:async';
import 'dart:io';
import 'package:get_storage/get_storage.dart';
import 'package:huadian/ext/env.dart';
import 'package:huadian/net/api.dart';
import 'package:huadian/net/result.dart';
import 'package:huadian/tools/alert_toast.dart';
import 'package:huadian/tools/config.dart';
import 'package:huadian/ext/extends.dart';
import 'package:huadian/tools/log_util.dart';
import 'package:huadian/util/pachage_version.dart';
import 'package:huadian/util/user_manager.dart';
class HjtDio {
static final HjtDio _manager = HjtDio._init();
static late Dio _dio;
final box = GetStorage();
HjtDio._init() {
BaseOptions options = BaseOptions(
baseUrl: GlobalConfig.saasUrl(),
connectTimeout: 1000 * 30,
// 连接服务器超时时间,单位是毫秒.
receiveTimeout: 1000 * 30,
// 响应流上前后两次接受到数据的间隔,单位为毫秒, 这并不是接收数据的总时限.
responseType: ResponseType.json,
contentType: "application/json",
);
PackageVersionInfo.instance!.init();
_dio = Dio(options);
// 添加拦截器
// if (GlobalConfig.env != Env.Formal) {
_dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
print("\n================== 请求数据 ==========================");
print("url = ${options.uri.toString()}");
print("headers = ${options.headers}");
LogUtil.d("data = ${options.data}");
LogUtil.d("params = ${options.queryParameters}");
return handler.next(options);
}, onResponse: (response, handler) {
print("\n================== 响应数据 ==========================");
print("code = ${response.statusCode}");
LogUtil.d(response);
print("\n");
return handler.next(response);
}));
// 这里加了 下面就捕获不到不能处理了
/*
, onError: (error, handler) {
print("\n================== 错误响应数据 ======================");
print("type = ${error.type}");
print("message = ${error.message}");
print("stackTrace = ${error.stackTrace}");
print("\n");
}
*/
// }
if (GlobalConfig.isProxyChecked) {
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
return GlobalConfig.isProxyChecked;
};
client.findProxy = (url) {
return 'PROXY ${GlobalConfig.proxy}';
};
return client;
};
}
}
factory HjtDio() {
return _manager;
}
Future<Result?> get<T>(
url,
param, {
bool token = true,
bool showLoading = true,
CancelToken? cancelToken,
String? contentType
}) async {
return await request<T>(url,
queryParameters: param, method: Method.get, token: token,cancelToken: cancelToken,contentType: contentType);
}
Future<Result?> post<T>(
url,
param, {
bool token = true,
bool showLoading = true,
CancelToken? cancelToken,
String? contentType
}) async {
return await request<T>(url,
data: param,
method: Method.post,
token: token,
showLoading: showLoading,
cancelToken: cancelToken,contentType: contentType);
}
Future<Result?> request<T>(String path,
{String method = 'GET',
String? contentType = "application/x-www-form-urlencoded;charset=utf-8",
// String contentType = "application/json; charset=UTF-8",
bool token = true,
bool showLoading = true,
dynamic data,
CancelToken? cancelToken,
Map<String, dynamic>? queryParameters,
Options? options}) async {
var connectivityResult = await (new Connectivity().checkConnectivity());
// 无网
if (connectivityResult == ConnectivityResult.none) {
return Result(ERROR_NETWORK,"无网络连接!请检查网络设置。",null);
}
Response response;
if (token) {
String? token = UserManager().getToken();
print("token = $token");
if (token == null) {
return Result(TOKEN_EXPIRE,"登录状态失效!请重新登录。",null);
}
_dio.options.headers.addAll({'token': token});
}
_dio.options.contentType = contentType;
_dio.options.method = method;
// 默认 网络请求自动展示加载中
if (showLoading) EasyLoading.show();
try {
response = await _dio.request(path,
queryParameters: queryParameters,
cancelToken: cancelToken,
data: data);
EasyLoading.dismiss();
// 不用判断也可 必为200
if (response.statusCode == 200) { // 服务器正常给 结果
try {
Result result = Result.fromJson(response.data);
// 可以在这 对不同的code码 进行处理 如:登录失效等
if (result.code == TOKEN_EXPIRE){
result.code = TOKEN_EXPIRE;
} else if(result.code != SUCCESS){
result.code = ERROR_PARAMS;
} // 其他额外的code
return result;
} catch (e,s) {
return Result(ERROR_CLIENT,"客户端错误!",null);
}
}
return Result(ERROR_CLIENT,"客户端错误!",null);
} catch (error, s) {
var result = Result(ERROR_CLIENT,"未知错误!",null);
if (error is DioError) {
debugPrint("\n================== 错误响应数据 ======================");
debugPrint("type = ${error.type}");
debugPrint("message = ${error.message}");
debugPrint("stackTrace = ${error.stackTrace}");
debugPrint("\n");
switch (error.type) {
case DioErrorType.connectTimeout:
case DioErrorType.sendTimeout:
case DioErrorType.receiveTimeout:
result = Result(ERROR_TIMEOUT,"网络请求超时错误!",null);
break;
case DioErrorType.response: // 服务器非200返回
// 非正常请求服务器200
if (showLoading)
AlertToast().showErrorAlertToast(error.message);
result = Result(ERROR_SERVICE,"服务器错误!",null);
break;
case DioErrorType.cancel:
result = Result(ERROR_CLIENT,"网络请求取消!",null);
break;
case DioErrorType.other:
if (showLoading)
AlertToast().showErrorAlertToast(error.message);
break;
}
}
return result;
}
}
// 不可用
Future<Result?> uploadImage({required String? path,CancelToken? cancelToken}) async {
if(path.isNullOrEmpty()){
return Result(ERROR_CLIENT,"图片路径为空!",null);
}
var name = path!.substring(path.lastIndexOf("/") + 1, path.length);
FormData formData = FormData.fromMap({
"color": "#e3e3e3",
"size": 16,
"iswatermark": 0,
// 没有水印
"file": MultipartFile.fromFile(path, filename: name),
});
// return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "application/octet-stream", method: Method.post,);
return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "multipart/form-data", method: Method.post,);
}
Future<Result?> uploadMemoryImage({required Uint8List uint8list,CancelToken? cancelToken,bool showLoading = true}) async {
FormData formData = FormData.fromMap({
"color": "#e3e3e3",
"size": 16,
"iswatermark": 0,
"file": MultipartFile.fromBytes(
uint8list,
// 文件名
filename: 'gallery.jpg',
// 文件类型
contentType: MediaType("image", "jpg"),
)
});
// return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "application/octet-stream", method: Method.post,);
return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "multipart/form-data", method: Method.post,showLoading: showLoading);
}
int failUploadNum = 0;
// 多张分开一个一个发 效果上实现 多图上传
Future<Result?> uploadMemoryImageByForge({required List<Uint8List> fileList,CancelToken? cancelToken}) async {
failUploadNum = 0;
int len = fileList.length;
for (int i = 0; i < len; i++) {
EasyLoading.show(status: "上传中...($i/$len) \n 失败个数: $failUploadNum");
try{ // 某一张出错 不影响其他的
Result? result = await uploadMemoryImage(uint8list: fileList[i],cancelToken: cancelToken,showLoading: false);
if(result?.code!=200){
failUploadNum = failUploadNum + 1;
}
}catch(e){}
}
EasyLoading.showToast("上传成功 ${len -failUploadNum}个 失败 $failUploadNum个",duration: Duration(milliseconds: 1500));
}
// 后端不支持 多张一起上传
Future<Result?> uploadMemoryImageList({required List<Uint8List> fileList,CancelToken? cancelToken}) async {
List<MultipartFile> files = [];
for (int i = 0; i < fileList.length; i++) {
files.add(MultipartFile.fromBytes(
fileList[i],
// 文件名
filename: 'gallery_$i.jpg',
// 文件类型
contentType: MediaType("image", "jpg"),
));
}
FormData formData = FormData.fromMap({
"color": "#e3e3e3",
"size": 16,
"iswatermark": 0,
"file": files
});
// return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "application/octet-stream", method: Method.post,);
return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "multipart/form-data", method: Method.post,);
}
// // 下载图片
// void downLoadImageSave2Gallery(String url,{CancelToken? cancelToken}) async {
// EasyLoading.show(status: "下载中...");
// try {
// // https://sv.huaji.com/huaji_saas_220107134805_2729.jpeg
// var response = await Dio().get(url,
// options: Options(responseType: ResponseType.bytes));
// final result = await ImageGallerySaver.saveImage(
// Uint8List.fromList(response.data),
// quality: 60,
// name: url.substring(url.lastIndexOf("/")));
// EasyLoading.showSuccess("已保存到系统相册!");
// print(result);
// } on DioError catch (e) {
// EasyLoading.showError("保存失败: $e");
// print('downloadFile error---------$e');
// }
// }
// 下载资源
// void downLoadResource2System(String url,{String? savePath}) async {
// try {
// String? path;
// if(savePath!=null){
// path = savePath + url.substring(url.lastIndexOf("/"));
// }else{
// var appDir = await getTemporaryDirectory();
// path = appDir.path + url.substring(url.lastIndexOf("/"));
// }
// await Dio().download(url, savePath,onReceiveProgress: (int count, int total){
// EasyLoading.showProgress(count/total,status: "下载中...");
// });
// final result = await ImageGallerySaver.saveFile(path);
// EasyLoading.showSuccess("已保存到手机!");
// print(result);
// }catch(e){
// EasyLoading.showError("下载失败: $e");
// print('downloadFile error---------$e');
// }
// }