Flutter GetX 骨架
Flutter Getx 骨架项目,使项目创建过程快速简便。
简介
当我们想开始一个新项目时,我们都会面临同样的问题,我们需要处理一些可重复的事情,例如:
- 主题(浅色/深色)和在共享偏好中存储当前主题?
- 本地化和在共享偏好中存储当前区域设置?️
- Firebase 消息?
- 通知设置?
- 安全的 API 请求和错误处理?
- 在 API 调用期间更改小部件(加载中、成功、失败等)?
- Snackbar、Toasts 和应用内通知?
- 使应用程序更具响应性并停止字体缩放⚖️ 此项目将处理所有这些可重复的事情,以便您可以在几个步骤内启动您的项目,并且所有提到的点都已设置好并准备好使用?
致谢
项目是使用 get_cli 创建的,这是一个非常有用的工具,可以帮助您(启动项目、创建屏幕/控制器、处理 DI)等,我们将列出其他帮助创建此骨架的包
- GetX 用于状态管理、导航、管理依赖项等
- flutter_screenutil 使应用程序更具响应性
- hive 作为本地数据库
- get_storage 作为共享偏好(它更简单,并且可以同步读取数据)
- awesome_notifications 用于本地通知
克隆并启动项目
在浏览文件夹之前,让我们先执行一些操作,使项目准备好启动
-
首先运行此命令,它将生成 hive 类型适配器(用于我们要本地存储的自定义类)
flutter packages pub run build_runner build --delete-conflicting-outputs如果您不想使用 hive,请注释掉 main.dart 中的这行
await MyHive.init(adapters: [UserModelAdapter()]);
-
为了使您的应用程序响应并与您的 (xd,figma..等) 设计完全一致,您需要在 main.dart 中为 flutter_ScreenUtil 设置画布尺寸
ScreenUtilInit( designSize: const Size(375, 812), // change this to your xd artboard size
-
FCM 和 Awesome Notifications 结合在同一个类中,所以每当您将应用程序连接到 firebase 时,您的应用程序将准备好接收通知,您所要做的就是通过实现 FcmHelper 类中的 sendFcmTokenToServer 方法来将 fcm 通知发送到您的 API?
static _sendFcmTokenToServer(){ var token = MySharedPref.getFcmToken(); // TODO SEND FCM TOKEN TO SERVER }
-
更改应用程序包名
flutter pub run change_app_package_name:main com.new.package.name -
更改应用程序名称
flutter pub run rename_app:main all="My App Name" -
更改应用程序启动图标(将 assets/images/app_icon.png 替换为您的应用程序图标),然后运行此命令
flutter pub run flutter_launcher_icons:main -
FCM:firebase 最近添加了“添加 flutter 应用”,这将使将我们的 flutter (android/ios) 应用添加到 firebase 只需 2 个步骤?但首先您需要下载 Firebase CLI 并在终端中执行
dart pub global activate flutterfire_cli然后按照 Firebase 的指南进行操作,您将得到一个类似于此的命令
flutterfire configure --project=flutter-firebase-YOUR_PROJECT_ID就是这样!您的项目现在已连接到 firebase,fcm 已准备就绪,可以接收通知
快速入门
-
响应式应用程序:为了使您的应用程序具有响应性,您需要利用 flutter_ScreenUtil,因此,与其使用普通的 double 值来设置高度、宽度、半径等,不如像这样使用它:
-
200.w // adapted to screen width 100.h // /Adapted to screen height 25.sp // adapter font size 10.r // adapter radius // Example Container( height: 100.h, width: 200.w, child: Text("Hello",style: TextStyle(fontSize: 20.sp,)) )
-
主题
-
更改主题
MyTheme.changeTheme();
-
检查当前主题
bool isThemeLight = MyTheme.getThemeIsLight();
-
-
本地化
-
更改应用程序区域设置
LocalizationService.updateLanguage('en');
-
获取当前区域设置
LocalizationService.getCurrentLocal();
-
使用翻译
Text(Strings.hello.tr)
-
-
安全的 API 调用
-
逻辑代码(在控制器中)
// api call status ApiCallStatus apiCallStatus = ApiCallStatus.holding; // getting data from api simulating getData() async { // *) indicate loading state apiCallStatus = ApiCallStatus.loading; update(); // *) perform api call await BaseClient.get( Constants.todosApiUrl, // url onSuccess: (response){ // api done successfully data = List.from(response.data); // -) indicate success state apiCallStatus = ApiCallStatus.success; update(); // update ui }, // if you dont pass this method base client // will automaticly handle error and show message onError: (error){ // show error message to user BaseClient.handleApiError(error); // -) indicate error status apiCallStatus = ApiCallStatus.error; update(); // update ui }, // error while performing request ); }
-
UI:MyWidgetsAnimator 将根据当前的 API 调用状态在小部件之间进行动画
GetBuilder<HomeController>( builder: (controller){ LocalizationService.updateLanguage('en'); LocalizationService.getCurrentLocal(); return MyWidgetsAnimator( apiCallStatus: controller.apiCallStatus, loadingWidget: () => const Center(child: CircularProgressIndicator(),), errorWidget: ()=> const Center(child: Text('Something went worng!'),), successWidget: () => ListView.separated( itemCount: controller.data!.length, separatorBuilder: (_,__) => SizedBox(height: 10.h,), itemBuilder: (ctx,index) => ListTile( title: Text(controller.data![index]['userId'].toString()), subtitle: Text(controller.data![index]['title']), ), ), ); }, )
-
-
Snackbars(应用内通知)
CustomSnackBar.showCustomSnackBar(title: 'Done successfully!', message: 'item added to wishlist'); CustomSnackBar.showCustomErrorSnackBar(title: 'Failed!', message: 'failed to load data'); CustomSnackBar.showCustomToast(message: 'added to card'); CustomSnackBar.showCustomErrorToast(message: 'added to card');
探索项目
设置好所有必需项后,现在让我们谈谈文件夹结构,该结构主要基于 Getx 模式,并包含一些个人意见。如果您打开 lib 文件夹,您会找到这些文件夹:
.
└── lib
├── app
│ ├── components
│ ├── data
│ │ ├── local
│ │ └── models
│ ├── modules
│ │ └── home
│ ├── routes
│ └── services
├── config
│ ├── theme
│ └── translation
└── utils
- app:将包含我们所有核心的应用程序逻辑
- components:将包含所有共享的 UI 小部件
- data:将包含我们的模型和本地数据源(本地数据库和共享偏好)
- modules:应用程序屏幕
- routes:由 get_cli 生成,并将包含我们的导航路由
- services:包含所有用于进行安全且干净的 API 调用的逻辑
- config:将包含应用程序配置,例如主题、本地化服务
- utils:用于我们的辅助类
功能
-
Theme:如果您打开 theme 包,您会看到这些文件
└── theme ├── dark_theme_colors.dart ├── light_theme_colors.dart ├── my_fonts.dart ├── my_styles.dart └── my_theme.dart您只需要更改应用程序颜色(light/dark_theme_colors),如果您想更改应用程序字体大小和系列,只需修改 my_fonts.dart,就这样,您无需担心样式和主题,您只需要编辑 my_syles.dart,如果您想更改某些元素的 theme data(padding、border 等),并且如果您想更改主题,只需使用此代码:
// change theme and save current theme state to shared pref MyTheme.changeTheme();
如果您想检查主题是深色/浅色,只需使用:
bool themeIsLight = MyTheme.getThemeIsLight(); // OR bool themeIsLight = MySharedPref.getThemeIsLight();
-
本地化/翻译我们将使用 getx 本地化系统,在正常情况下代码看起来会是这样的:
class LocalizationService extends Translations { @override Map<String, Map<String, String>> get keys => { 'en_US': { 'hello' : 'Hello' }, 'ar_AR': { 'hello' : 'مرحباً' }, }; } Text('hello'.tr); // translated text
但是因为我们有很多单词需要翻译,我们将把键文件(strings_enum.dart)和语言映射分离到不同的类中,所以代码将变成这样:
class LocalizationService extends Translations { @override Map<String, Map<String, String>> get keys => { 'en_US': enUs, 'ar_AR': arAR, }; } // keys class Strings { static const String hello = 'hello'; } // english words const Map<String, String> enUs = { Strings.hello : 'Hello', } // arabic translate final Map<String, String> arAR = { Strings.hello : 'مرحبا', } //result Text(Strings.hello.tr)
这解释了为什么我们的翻译包中有这个文件结构
└── translations ├── ar_Ar │ └── ar_ar_translation.dart ├── en_US │ └── en_us_translation.dart ├── localization_service.dart └── strings_enum.dart要更改语言,您将使用:
LocalizationService.updateLanguage('en');
要获取当前区域设置/语言,您可以使用:
LocalizationService.getCurrentLocal(); // OR MySharedPref.getCurrentLocal();
-
安全的 API 调用:在 lib/app/services 包下,您会找到 3 个文件
- api_call_status.dart:包含我们 API 调用所有可能的阶段(加载中、成功、错误等)
- api_exception.dart:自定义异常类,使错误处理更具信息量
- base_client.dart:包含我们的安全 API 调用函数,用于正确执行 API 请求,您可以这样做:
class HomeController extends GetxController {
// hold data
List<dynamic>? data;
// api call status
ApiCallStatus apiCallStatus = ApiCallStatus.holding;
// getting data from api simulating
getData() async {
// *) indicate loading state
apiCallStatus = ApiCallStatus.loading;
update();
// *) perform api call
await BaseClient.get(
Constants.todosApiUrl, // url
onSuccess: (response){ // api done successfully
data = List.from(response.data);
// -) indicate success state
apiCallStatus = ApiCallStatus.success;
update(); // update ui
},
// if you dont pass this method base client
// will automaticly handle error and show message
onError: (error){
// show error message to user
BaseClient.handleApiError(error);
// -) indicate error status
apiCallStatus = ApiCallStatus.error;
update(); // update ui
}, // error while performing request
);
}
@override
void onInit() {
getData();
super.onInit();
}
}
base client 将捕获所有可能的错误,如果您没有传递 onError 函数,它将自动在 UI 端捕获错误,代码将是:
GetBuilder<HomeController>(
builder: (_){
return MyWidgetsAnimator(
apiCallStatus: controller.apiCallStatus,
loadingWidget: () => const Center(child: CircularProgressIndicator(),),
errorWidget: ()=> const Center(child: Text('Something went worng!'),),
successWidget: () =>
ListView.separated(
itemCount: controller.data!.length,
separatorBuilder: (_,__) => SizedBox(height: 10.h,),
itemBuilder: (ctx,index) => ListTile(
title: Text(controller.data![index]['userId'].toString()),
subtitle: Text(controller.data![index]['title']),
),
),
);
},
)
注意: MyWidgetsAnimator 将负责 UI 随动画的变化,您将传递 ApiCallStatus 和 success、failed、loading 等小部件,它将负责过渡。
支持
如需支持,请发送电子邮件至 [email protected] 或 Facebook Emad Beltaje。



