倉(cāng)庫(kù)地址:github.com/xieyezi/flu…
說在前面
Hi,小伙伴們好久不見,這次帶來一篇flutter + getx 的實(shí)踐文章。
基于getx 實(shí)現(xiàn)的全新flutter getx 模版,適用于中大型項(xiàng)目的開發(fā)。
- ??
flutter最新版本的空安全 - ??
view和邏輯完全解耦 - ?
view和state自動(dòng)響應(yīng) - ??
dio、shared_preferences等通用模塊的封裝 - ?? 去
context化
環(huán)境
Flutter 2.2.0 ? channel stable ? https://github.com/flutter/flutter.git
Framework ? revision b22742018b (3 weeks ago) ? 2021-05-14 19:12:57 -0700
Engine ? revision a9d88a4d18
Tools ? Dart 2.13.0
復(fù)制代碼
lib目錄劃分
common
此目錄用來存放通用模塊及其變量,例如colors、langs、values等,例如:
├── colors
│ └── colors.dart
├── langs
│ ├── en_US.dart
│ ├── translation_service.dart
│ └── zh_Hans.dart
└── values
├── cache.dart
├── storage.dart
└── values.dart
復(fù)制代碼
components
此目錄主要存放頂層公告組件,例如 appbar、scaffold、dialog等等,例如:
├── components.dart
├── custom_appbar.dart
└── custom_scaffold.dart
復(fù)制代碼
pages
此目錄主要存放頁(yè)面文件,例如:
注:每個(gè)Item為一個(gè)文件夾.
├── Index
├── home
├── login
├── notfound
├── proxy
└── splash
復(fù)制代碼
router
此目錄為路由文件,此模版的路由方式約定為命名路由,為固定目錄,目錄結(jié)構(gòu)如下:
├── app_pages.dart
└── app_routes.dart
復(fù)制代碼
services
此目錄用來存放API,例如:
├── services.dart
└── user.dart // 關(guān)于用戶的API
復(fù)制代碼
utils
此目錄用來存放一些工具模塊,例如 request 、local_storage等等,例如:
├── authentication.dart
├── local_storage.dart
├── request.dart
├── screen_device.dart
└── utils.dart
復(fù)制代碼
開發(fā)規(guī)范
當(dāng)你需要新建一個(gè)頁(yè)面時(shí),你需要按照以下步驟進(jìn)行:
假設(shè)我們現(xiàn)在要?jiǎng)?chuàng)建一個(gè)
Home頁(yè)面.
- 在
pages目錄下新建home目錄:
// pages
$ mkdir home
$ cd home
復(fù)制代碼
- 在
home目錄下,新建以下四個(gè)文件:
-
home_view.dart: 視圖(用來實(shí)現(xiàn)頁(yè)面布局) -
home_contrller.dart: 控制器(用來實(shí)現(xiàn)業(yè)務(wù)邏輯) -
home_binding: 控制器綁定(用來綁定controller到view) -
home_model: 數(shù)據(jù)模型(用來約定數(shù)據(jù)模型)
注意:每創(chuàng)建一個(gè)頁(yè)面時(shí),都必須如此做,命名采用 '頁(yè)面名_key' 這樣的形式。
當(dāng)你創(chuàng)建好一個(gè)頁(yè)面,目錄應(yīng)該長(zhǎng)這樣??:
// home
.
├── home.binding.dart
├── home_controller.dart
├── home_model.dart
└── home_view.dart
復(fù)制代碼
- 到
router文件夾下面添加對(duì)應(yīng)路由:
// app_routes.dart
part of 'app_pages.dart';
abstract class AppRoutes {
...
static const Home = '/home';
...
}
復(fù)制代碼
// app_pages.dart
class AppPages {
static final routes = [
...
GetPage(
name: AppRoutes.Home,
page: () => HomePage(),
binding: HomeBinding(),
),
...
];
}
復(fù)制代碼
完成以上步驟,你就可以愉快的開始開發(fā)了。
狀態(tài)管理
contrller 是我們實(shí)現(xiàn)業(yè)務(wù)邏輯的地方,為什么我們要將 業(yè)務(wù)邏輯和視圖分開呢?因?yàn)?code>flutter 的意大利面式的代碼實(shí)在是太難維護(hù)了,本來flutter 的頁(yè)面布局和樣式寫在一起就很惡心了,再加上業(yè)務(wù)邏輯代碼的話,實(shí)在太難以維護(hù),而且,如果我們想要擁有狀態(tài)的話,我們的頁(yè)面不得不繼承自stateful widget,性能損耗太嚴(yán)重了。
所以我們利用 getx 提供的 controller,將我們的業(yè)務(wù)邏輯和視圖解耦。
一個(gè)標(biāo)準(zhǔn)的contrller長(zhǎng)這樣:
class HomeController extends GetxController {
final count = 0.obs;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {}
@override
void onClose() {}
void increment() => count.value++;
}
復(fù)制代碼
當(dāng)我們需要一個(gè)響應(yīng)式的變量時(shí),我們只需在變量的后面加一個(gè).obs,例如:
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// 甚至自定義類 - 可以是任何類
final user = User().obs;
復(fù)制代碼
值得注意的是,因?yàn)楝F(xiàn)在
flutter有了null-safety,所以我們最好給響應(yīng)式變量一個(gè)初始值。
當(dāng)我們?cè)?code>controller更新了響應(yīng)式變量時(shí),視圖會(huì)自動(dòng)更新渲染。
但是實(shí)際上,你也可以不定義這種響應(yīng)式變量,例如我們可以這樣:
class HomeController extends GetxController {
int count = 0;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {}
@override
void onClose() {}
void increment() {
count++;
update();
}
}
復(fù)制代碼
這樣和.obs的唯一區(qū)別是,我們需要手動(dòng)調(diào)用 update() 更新狀態(tài)的變化,這樣view才能在count變化時(shí),收到我們的通知重新渲染。
我們應(yīng)該將發(fā)起請(qǐng)求,放在onInit鉤子里面,例如進(jìn)入訂單頁(yè)面時(shí),我們應(yīng)該獲取訂單信息,就如同在 stateful wdiget 里面的init鉤子一樣。
視圖
首先,你需要將你的class 繼承自 GetxView<T>(T 為你的Controller),例如:
class HomePage extends GetView<HomeController> {
HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}
復(fù)制代碼
GetxView<HomeController> 會(huì)自動(dòng)幫你把 Controller 注入到 view 中,你可以簡(jiǎn)單理解為它自動(dòng)幫你執(zhí)行了以下步驟
final controller = Get.find<HomeController>();
復(fù)制代碼
不必?fù)?dān)心 GetxView<T> 的性能,因?yàn)樗鼉H僅是繼承自 Stateless Widget ,記住,有了 getx 你完全不需要 Stateful Widget
當(dāng)我們想要綁定controller的變量時(shí),我們約定了兩種方法:
Obx(()=>)
如果你的變量是.obs的,那么我們就使用Obx(()=>),它會(huì)在變量變更時(shí)自動(dòng)刷新view,例如:
// home_contrller
class HomeController extends GetxController {
final count = 0.obs;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {}
@override
void onClose() {}
void increment() => count.value++;
}
復(fù)制代碼
在view里面使用 Obx(()=>) 綁定count:
// home_view
class HomePage extends GetView<HomeController> {
HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Obx(() => Center(child: Text(controller.count.toString()))),
),
);
}
}
復(fù)制代碼
GetBuilder<T>
如果你的變量不是.obs的,那么我們就使用GetBuilder<T>,例如:
class HomeController extends GetxController {
int count = 0;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {}
@override
void onClose() {}
void increment() {
count++;
update();
}
}
復(fù)制代碼
在 view 里面使用 GetBuilder<T> 綁定count:
class HomePage extends GetView<HomeController> {
HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BaseScaffold(
appBar: MyAppBar(
centerTitle: true,
title: MyTitle('首頁(yè)'),
leadingType: AppBarBackType.None,
),
body: Container(
child: GetBuilder<HomeController>(builder: (_) {
return Center(child: Text(controller.count.toString()));
}),
),
);
}
}
復(fù)制代碼
其實(shí)getx還提供了其他的render function,但是為了減少心智負(fù)擔(dān)和復(fù)雜度,我們就使用這兩種就夠了。
路由管理
這里我們采用了getx提供的命名式路由,如果你學(xué)過vue,那么幾乎沒有學(xué)習(xí)成本。
假設(shè)我們現(xiàn)在添加了一個(gè)頁(yè)面,叫做List,然后我們需要到router文件夾下面去配置它:
// app_routes.dart
part of 'app_pages.dart';
abstract class AppRoutes {
...
static const List = '/list';
...
}
復(fù)制代碼
// app_pages.dart
class AppPages {
static final routes = [
...
GetPage(
name: AppRoutes.Home,
page: () => ListPage(),
binding: ListBinding(),
),
...
];
}
復(fù)制代碼
這個(gè)List對(duì)應(yīng)的假設(shè)是訂單列表,當(dāng)我們點(diǎn)擊列表中某個(gè)訂單時(shí),我們通常會(huì)進(jìn)入到訂單詳情頁(yè)面,所以我們此時(shí)應(yīng)再添加一個(gè)詳情頁(yè)面:
// app_routes.dart
part of 'app_pages.dart';
abstract class AppRoutes {
...
static const List = '/list';
static const Detaul = '/detail';
...
}
復(fù)制代碼
// app_pages.dart
class AppPages {
static final routes = [
...
GetPage(
name: AppRoutes.Home,
page: () => ListPage(),
binding: ListBinding(),
children: [
GetPage(
name: AppRoutes.Detail,
page: () => DetailPage(),
binding: DetailBinding(),
),
],
),
...
];
}
復(fù)制代碼
因?yàn)樵斍轫?yè)面和列表頁(yè)面有先后級(jí)關(guān)系,所以我們可以將 Detail 頁(yè)面,放到 List 的children 下面,當(dāng)然你也可以不這樣做。
當(dāng)我們使用時(shí):
Get.toNamed('/list/detail');
復(fù)制代碼
其他路由鉤子:
瀏覽并刪除前一個(gè)頁(yè)面:
Get.offNamed("/NextScreen");
復(fù)制代碼
瀏覽并刪除所有以前的頁(yè)面:
Get.offAllNamed("/NextScreen");
復(fù)制代碼
傳遞參數(shù):
Get.toNamed("/NextScreen", arguments: {id: 'xxx'});
復(fù)制代碼
參數(shù)的類型可以是一個(gè)字符串,一個(gè)Map,一個(gè)List,甚至一個(gè)類的實(shí)例。
獲取參數(shù):
print(Get.arguments);
// print out: `{id: 'xxx'}`
復(fù)制代碼
使用 getx 的路由它有一個(gè)非常好的優(yōu)點(diǎn),那就是它是去context化的。還記得我們以前被context 支配的恐懼嗎? 有了getx,它將不復(fù)存在。
使用 monia-cli 進(jìn)行開發(fā)
我們很高興,能將 flutter-getx-template 加入到 monia-cli。
利用 monia-cli 新建flutter項(xiàng)目:
monia create <project-name>
復(fù)制代碼
? Desktop monia create flutter_demo
? Which framework do you want to create Flutter
? Which flutter version do you want to create null-safety
? Please input your project description description
? Please input project version 1.0.0
? Creating project in /Users/xieyezi/Desktop/flutter_demo.
?? Initializing git repository....
.......
? Download template from monia git repository... This might take a while....
?? Successfully created project flutter_demo.
?? Get started with the following commands:
$ cd flutter_demo
$ flutter run
_ _ _
_ __ ___ ___ _ __ (_) __ _ ___| (_)
| '_ ` _ \ / _ \| '_ \| |/ _` |_____ / __| | |
| | | | | | (_) | | | | | (_| |_____| (__| | |
|_| |_| |_|\___/|_| |_|_|\__,_| \___|_|_|
復(fù)制代碼
不僅如此, monia-cli 還提供了快速生成一個(gè) flutter getx 頁(yè)面的功能。
假如現(xiàn)在你想生成一個(gè) order_sending 新頁(yè)面,你只需在 pages 目錄下面輸入:
monia init order_sending
復(fù)制代碼
? pages monia init order_sending
? Generate page in /Users/xieyezi/Desktop/flutter_demo/lib/pages/order_sending.
? Generating, it's will not be wait long...
generate order_sending lib success.
generate /Users/xieyezi/Desktop/flutter_demo/lib/pages/order_sending/order_sending_view.dart file success.
generate /Users/xieyezi/Desktop/flutter_demo/lib/pages/order_sending/order_sending_controller.dart file success.
generate /Users/xieyezi/Desktop/flutter_demo/lib/pages/order_sending/order_sending_binding.dart file success.
?? Successfully generate page order_sending.
復(fù)制代碼
vscode 插件
monia 還提供了vscode 插件: monia-vscode-extension
點(diǎn)擊左下角的monia-generate 文字按鈕,輸入pageName,即可在pages目錄下新建一個(gè)flutter getx page: