共计 7321 个字符,预计需要花费 19 分钟才能阅读完成。
本节指标
- GetConnect
- StateMixin
- GetController + Dio
- SuperController
视频
https://www.bilibili.com/vide…
代码
https://github.com/ducafecat/…
参考
- https://pub.flutter-io.cn/pac…
注释
GetConnect
- 瞎聊设计模式
Provider
提供者模式 位于高层 由他来决定从哪里、提供什么
绝对应的有 Consumer
消费者模式
Repository
模式,这层有 OO
面向对象的意思,用来解决拉取数据细节,这样到 Controller
控制器 这一层只有解决业务就行,可不便测试
DAO
就是纯正的数据拜访层,没有 00
的概念
Service
Model
Entity
…
前端其实对数据加工、面向服务、畛域模型偏弱,更多的是组件拆分、款式、布局,这才是要关系的,就算是测试也是 E2E
偏重不同。
E2E
(End To End)即端对端测试,属于黑盒测试,通过编写测试用例,自动化模仿用户操作,确保组件间通信失常,程序流数据传递如预期。
- 封装 GetConnect
lib/common/utils/base_provider.dart
class BaseProvider extends GetConnect {
@override
void onInit() {
httpClient.baseUrl = SERVER_API_URL;
// 申请拦挡
httpClient.addRequestModifier<void>((request) {request.headers['Authorization'] = '12345678';
return request;
});
// 响应拦挡
httpClient.addResponseModifier((request, response) {return response;});
}
}
- Provider
lib/pages/getConnect/provider.dart
abstract class INewsProvider {Future<Response<NewsPageListResponseEntity>> getNews();
}
class NewsProvider extends BaseProvider implements INewsProvider {
// 新闻分页
// @override
// Future<Response<NewsPageListResponseEntity>> getNews() => get("/news");
@override
Future<Response<NewsPageListResponseEntity>> getNews() async {var response = await get("/news");
var data = NewsPageListResponseEntity.fromJson(response.body);
return Response(
statusCode: response.statusCode,
statusText: response.statusText,
body: data,
);
}
}
- Repository
lib/pages/getConnect/repository.dart
abstract class INewsRepository {Future<NewsPageListResponseEntity> getNews();
}
class NewsRepository implements INewsRepository {NewsRepository({required this.provider});
final INewsProvider provider;
@override
Future<NewsPageListResponseEntity> getNews() async {final response = await provider.getNews();
if (response.status.hasError) {return Future.error(response.statusText!);
} else {return response.body!;}
}
}
- Controller
lib/pages/getConnect/controller.dart
class NewsController extends SuperController<NewsPageListResponseEntity> {NewsController({required this.repository});
final INewsRepository repository;
@override
void onInit() {super.onInit();
//Loading, Success, Error handle with 1 line of code
// append(() => repository.getNews);
}
// 拉取新闻列表
Future<void> getNewsPageList() async {append(() => repository.getNews);
}
@override
void onReady() {
print('The build method is done.'
'Your controller is ready to call dialogs and snackbars');
super.onReady();}
@override
void onClose() {print('onClose called');
super.onClose();}
@override
void didChangeMetrics() {print('the window size did change');
super.didChangeMetrics();}
@override
void didChangePlatformBrightness() {print('platform change ThemeMode');
super.didChangePlatformBrightness();}
@override
Future<bool> didPushRoute(String route) {print('the route $route will be open');
return super.didPushRoute(route);
}
@override
Future<bool> didPopRoute() {print('the current route will be closed');
return super.didPopRoute();}
@override
void onDetached() {print('onDetached called');
}
@override
void onInactive() {print('onInative called');
}
@override
void onPaused() {print('onPaused called');
}
@override
void onResumed() {print('onResumed called');
}
}
- GetView
lib/pages/getConnect/view.dart
class NewsView extends GetView<NewsController> {NewsView({Key? key}) : super(key: key);
_buildListView(NewsPageListResponseEntity? state) {
return ListView.separated(
itemCount: state != null ? state.items!.length : 0,
itemBuilder: (context, index) {final NewsItem item = state!.items![index];
return ListTile(onTap: () => null,
title: Text(item.title),
trailing: Text("分类 ${item.category}"),
);
},
separatorBuilder: (BuildContext context, int index) {return Divider();
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("GetConnect Page"),
),
body: controller.obx((state) => _buildListView(state),
onEmpty: Text("onEmpty"),
onLoading: Center(
child: Column(
children: [Text("没有数据"),
ElevatedButton(onPressed: () {controller.getNewsPageList();
},
child: Text('拉取数据'),
),
],
),
),
onError: (err) => Text("onEmpty" + err.toString()),
),
);
}
}
- Bindings
lib/pages/getConnect/bindings.dart
class NewsBinding implements Bindings {
@override
void dependencies() {Get.lazyPut<INewsProvider>(() => NewsProvider());
Get.lazyPut<INewsRepository>(() => NewsRepository(provider: Get.find()));
Get.lazyPut(() => NewsController(repository: Get.find()));
}
}
- 路由
lib/common/routes/app_pages.dart
GetPage(
name: AppRoutes.GetConnect,
binding: NewsBinding(),
page: () => NewsView(),
),
StateMixin
雷同代码不再反复
- 控制器 Mixin 如下
lib/pages/getConnect_stateMixin/controller.dart
class NewsStateMixinController extends GetxController
with StateMixin<NewsPageListResponseEntity> {
final NewsStateMixinProvider provider;
NewsStateMixinController({required this.provider});
// 拉取新闻列表
Future<void> getNewsPageList() async {
// 获取数据
final Response response = await provider.getNews();
// 判断,如果有谬误
if (response.hasError) {
// 扭转数据,传入谬误状态,在 ui 中会解决这些谬误
change(null, status: RxStatus.error(response.statusText));
} else {
// 否则,存储数据,扭转状态为胜利
var data = NewsPageListResponseEntity.fromJson(response.body);
change(data, status: RxStatus.success());
}
}
}
这种形式的确简化了很多代码
GetController + Dio
这种形式就是之前 Flutter 新闻客户端 的写法,能复用原来的 dio 代码。
- dio 根底类
lib/common/utils/http.dart
/*
* http 操作类
*
* 手册
* https://github.com/flutterchina/dio/blob/master/README-ZH.md
*
* 从 3 降级到 4
* https://github.com/flutterchina/dio/blob/master/migration_to_4.x.md
*/
class HttpUtil {static HttpUtil _instance = HttpUtil._internal();
factory HttpUtil() => _instance;
late Dio dio;
HttpUtil._internal() {
// BaseOptions、Options、RequestOptions 都能够配置参数,优先级别顺次递增,且能够依据优先级别笼罩参数
BaseOptions options = new BaseOptions(
// 申请基地址, 能够蕴含子门路
baseUrl: SERVER_API_URL,
// baseUrl: storage.read(key: STORAGE_KEY_APIURL) ?? SERVICE_API_BASEURL,
// 连贯服务器超时工夫,单位是毫秒.
connectTimeout: 10000,
// 响应流上前后两次承受到数据的距离,单位为毫秒。receiveTimeout: 5000,
// Http 申请头.
headers: {},
/// 申请的 Content-Type,默认值是 "application/json; charset=utf-8".
/// 如果您想以 "application/x-www-form-urlencoded" 格局编码申请数据,
/// 能够设置此选项为 `Headers.formUrlEncodedContentType`, 这样[Dio]
/// 就会自动编码申请体.
contentType: 'application/json; charset=utf-8',
/// [responseType] 示意冀望以那种格局 (形式) 承受响应数据。/// 目前 [ResponseType] 承受三种类型 `JSON`, `STREAM`, `PLAIN`.
///
/// 默认值是 `JSON`, 当响应头中 content-type 为 "application/json" 时,dio 会主动将响应内容转化为 json 对象。/// 如果想以二进制形式承受响应数据,如下载一个二进制文件,那么能够应用 `STREAM`.
///
/// 如果想以文本 (字符串) 格局接管响应数据,请应用 `PLAIN`.
responseType: ResponseType.json,
);
dio = new Dio(options);
// Cookie 治理
CookieJar cookieJar = CookieJar();
dio.interceptors.add(CookieManager(cookieJar));
}
/// restful get 操作
Future get(
String path, {
dynamic? queryParameters,
Options? options,
}) async {
var response = await dio.get(
path,
queryParameters: queryParameters,
options: options,
);
return response.data;
}
}
- api 定义
lib/common/apis/news.dart
/// 新闻
class NewsAPI {
/// 翻页
static Future<NewsPageListResponseEntity> newsPageList({NewsRecommendRequestEntity? param}) async {var response = await HttpUtil().get(
'/news',
queryParameters: param?.toJson(),);
return NewsPageListResponseEntity.fromJson(response);
}
}
- 控制器
lib/pages/getController_dio/controller.dart
class NewsDioController extends GetxController {
var newsPageList =
Rx<NewsPageListResponseEntity>(NewsPageListResponseEntity());
@override
void onInit() {super.onInit();
print("onInit");
}
@override
void onClose() {super.onClose();
print("onClose");
}
getPageList() async {newsPageList.value = await NewsAPI.newsPageList();
}
}
© 猫哥
https://ducafecat.tech/
https://github.com/ducafecat
往期
开源
GetX Quick Start
https://github.com/ducafecat/…
新闻客户端
https://github.com/ducafecat/…
strapi 手册译文
https://getstrapi.cn
微信探讨群 ducafecat
系列汇合
Dart 编程语言根底
https://space.bilibili.com/40…
Flutter 零根底入门
https://space.bilibili.com/40…
Flutter 实战从零开始 新闻客户端
https://space.bilibili.com/40…
Flutter 组件开发
https://space.bilibili.com/40…
Flutter Bloc
https://space.bilibili.com/40…
Flutter Getx4
https://space.bilibili.com/40…
Docker Yapi
https://space.bilibili.com/40…