Mustang
一个用于构建 Flutter 应用程序的框架。开箱即用,提供以下功能。
- 状态管理
- 持久化
- 缓存
- 文件布局和命名标准
- 通过 open_mustang_cli 减少样板代码
框架组件
-
Screen – Screen 是一个可复用的 widget。它通常代表应用中的一个屏幕或浏览器中的一个页面。
-
Model – 一个代表应用程序数据的 Dart 类。
-
State – 提供对
Screen所需的Model子集的访问。它是一个具有一个或多个Model字段的 Dart 类。 -
Service – 一个用于异步通信和业务逻辑的 Dart 类。
组件通信
-
每个
Screen都有一个对应的Service和一个State。这三个组件协同工作,在应用程序状态发生变化时持续
重建 UI。Screen在构建 UI 时读取StateScreen响应用户事件(如滚动、点击等)调用Service中的方法Service- 读取/更新
WrenchStore中的Model - 根据需要进行 API 调用
- 如果
WrenchStore被修改,则通知State
- 读取/更新
State通知Screen重建 UI- 返回步骤 1
持久化
默认情况下,app state 由 WrenchStore 在内存中维护。当应用程序终止时,app state 会永久
丢失。但是,在某些情况下,需要持久化和恢复 app state。例如:
- 保存并恢复用户的会话令牌,以防止用户每次都必须登录
- 保存并恢复屏幕中的部分更改,以便可以从用户离开的地方恢复工作。
启用持久化很简单,并且可以透明地工作。
import 'package:xxx/src/models/serializers.dart' as app_serializer;
WidgetsFlutterBinding.ensureInitialized();
// In main.dart before calling runApp method,
// 1. Enable persistence like below
WrenchStore.config(
isPersistent: true,
storeName: 'myapp',
);
// 2. Initialize persistence
Directory dir = await getApplicationDocumentsDirectory();
await WrenchStore.initPersistence(dir.path);
// 3. Restore persisted state before the app starts
await WrenchStore.restoreState(app_serializer.json2Type, app_serializer.serializerNames);
通过以上更改,app state (WrenchStore) 将持久化到磁盘,并在应用程序启动时恢复到 WrenchStore 中。
缓存
Cache 功能允许在需要时在相同类型的实例之间切换。
Persistence 是内存中 app state (WrenchStore) 的快照。但是,有时需要持久化数据
但仅在需要时才恢复。一个例子是技术人员同时处理多个工作,即技术人员在工作之间切换。
由于 WrenchStore 只允许一种类型的实例,因此 WrenchStore 中不能有两个 Job 对象实例。
Service 中可用的 Cache API 可以轻松地将任何实例恢复到内存 (WrenchStore)。
-
Future<void> addObjectToCache<T>(String key, T t)将类型
T的实例保存在缓存中。key是一个或多个缓存对象的标识符。 -
Future<void> deleteObjectsFromCache(String key)删除缓存中所有带有标识符
key的缓存对象 -
static Future<void> restoreObjects( String key, void Function( void Function<T>(T t) update, String modelName, String jsonStr, ) callback, )将缓存中由
key标识的所有对象恢复到内存WrenchStore和持久化存储中
以便内存和持久化的应用程序状态保持一致。 -
bool itemExistsInCache(String key)如果缓存中存在标识符
key,则返回true,否则返回false。
Model
-
一个用
appModel注释的类 -
Model 名称应以
$开头 -
使用
InitField注释初始化字段 -
方法/Getter/Setter 不支持在
Model类中 -
如果一个字段在
Model被持久化时应该被排除,则用SerializeField(false)注释该字段。@appModel class $User { late String name; late int age; @InitField(false) late bool admin; @InitField(['user', 'default']) late BuiltList<String> roles; late $Address address; // $Address is another model annotated with @appModel late BuiltList<$Vehicle> vehicles; // Use immutable versions of List/Map inside Model classes @SerializeField(false) late String errorMsg; // errorMsg field will not be included when $User model is persisted }
State
-
一个用
screenState注释的类 -
State 名称应以
$开头 -
类的字段必须是
Model类@screenState class $ExampleScreenState { late $User user; late $Vehicle vehicle; }
Service
-
一个用
ScreenService注释的类 -
将
State类作为参数提供给ScreenService注释,以在State和Service之间创建关联,如下所示。@ScreenService(screenState: $ExampleScreenState) class ExampleScreenService { void getUser() { User user = WrenchStore.get<User>() ?? User(); updateState1(user); } }
-
Service 还提供以下 API
-
updateState– 更新屏幕状态和/或重新构建屏幕。要更新State而不重新构建屏幕,请将reload参数设置为false。updateState()updateState1(T model1, { reload: true })updateState2(T model1, S model2, { reload: true })updateState3(T model1, S model2, U model3, { reload: true })updateState4(T model1, S model2, U mode3, V model4, { reload: true })
-
memoizeScreen– 仅调用一次作为参数传递的任何方法。T memoizeScreen<T>(T Function() methodName)// In the snippet below, getScreenData method caches the return value of getData method, a Future. // Even when getData method is called multiple times, method execution happens only the first time. Future<void> getData() async { Common common = WrenchStore.get<Common>() ?? Common(); User user; Vehicle vehicle; ... } Future<void> getScreenData() async { return memoize(getData); }
-
clearMemoizedScreen– 清除memoizeScreen方法缓存的值。void clearMemoizedScreen()Future<void> getData() async { ... } Future<void> getScreenData() async { return memoizeScreen(getData); } void resetScreen() { // clears Future<void> cached by memoizeScreen() clearMemoizedScreen(); }
-
Screen
-
使用
StateProviderwidget 在State发生变化时自动重新构建Screen。... Widget build(BuildContext context) { return StateProvider<HomeScreenState>( state: HomeScreenState(), child: Builder( builder: (BuildContext context) { // state variable provides access to model fields declared in the HomeScreenState class HomeScreenState? state = StateConsumer<HomeScreenState>().of(context); # Even when this widget is built many times, only 1 API call # will be made because the Future from the service is cached SchedulerBinding.instance?.addPostFrameCallback( (_) => HomeScreenService().getScreenData(), ); if (state?.common?.busy ?? false) { return Spinner(); } if (state?.counter?.errorMsg.isNotEmpty ?? false) { return ErrorBody(errorMsg: state.common.errorMsg); } return _body(state, context); }, ), ); }
文件夹结构
- 使用此框架创建的 Flutter 应用程序的文件夹结构如下所示:
lib/ - main.dart - src - models/ - model1.dart - model2.dart - screens/ - first/ - first_screen.dart - first_state.dart - first_service.dart - second/ - second_screen.dart - second_state.dart - second_service.dart - 每个
Screen都需要一个State和一个Service。因此,Screen、State、Service文件被分组在一个目录中。 - 所有
Model类都必须放在models目录中。
快速入门
-
安装 Flutter
mkdir -p ~/lib && cd ~/lib git clone https://github.com/flutter/flutter.git -b stable # Add PATH in ~/.zshrc export PATH=$PATH:~/lib/flutter/bin export PATH=$PATH:~/.pub-cache/bin
-
安装 Mustang CLI
dart pub global activate open_mustang_cli
-
创建 Flutter 项目
cd /tmp flutter create quick_start cd quick_start # Open the project in editor of your choice # vscode - code . # IntelliJ - idea .
-
更新
pubspec.yaml... dependencies: ... built_collection: ^5.1.1 built_value: ^8.1.3 mustang_core: ^1.0.2 path_provider: ^2.0.6 dev_dependencies: ... build_runner: ^2.1.4 mustang_codegen: ^1.0.3
-
安装依赖项
flutter pub get
-
为名为
counter的屏幕生成文件。以下命令将创建代表Model的文件,以及代表Screen、Service和State的文件。omcli -s counter
-
生成运行时文件并监视更改。
omcli -w # omcli -b generates runtime files once -
更新生成的
counter.dart模型class $Counter { @InitField(0) late int value; }
-
更新生成的
counter_screen.dart屏幕import 'package:flutter/material.dart'; import 'package:mustang_core/mustang_widgets.dart'; import 'counter_service.dart'; import 'counter_state.state.dart'; class CounterScreen extends StatelessWidget { const CounterScreen({ Key key, }) : super(key: key); @override Widget build(BuildContext context) { return StateProvider<CounterState>( state: CounterState(), child: Builder( builder: (BuildContext context) { CounterState? state = StateConsumer<CounterState>().of(context); return _body(state, context); }, ), ); } Widget _body(CounterState? state, BuildContext context) { int counter = state?.counter?.value ?? 0; return Scaffold( appBar: AppBar( title: Text('Counter'), ), body: Center( child: Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text('$counter'), ), ElevatedButton( onPressed: CounterService().increment, child: const Text('Increment'), ), ], ), ), ); } }
-
更新生成的
counter_service.dart服务import 'package:mustang_core/mustang_core.dart'; import 'package:quick_start/src/models/counter.model.dart'; import 'counter_service.service.dart'; import 'counter_state.dart'; @ScreenService(screenState: $CounterState) class CounterService { void increment() { Counter counter = WrenchStore.get<Counter>() ?? Counter(); counter = counter.rebuild((b) => b.value = (b.value ?? 0) + 1); updateState1(counter); } }
-
更新
main.dart... Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( ... primarySwatch: Colors.blue, ), home: CounterScreen(), // Point to Counter screen ); } ...


