Flutter动态表达式求值插件flutter_eval的使用
Flutter动态表达式求值插件flutter_eval的使用
flutter_eval
是一个为 dart_eval
提供的 Flutter 桥接库,它是一个解决方案,可以实现代码推送、动态小部件和运行时 Flutter 代码评估。它可以用于通过空中更新热插拔应用程序的部分内容,从服务器动态加载用户界面,或基于用户输入(如计算器)进行代码评估。flutter_eval
支持所有平台,包括 iOS,并且基于 dart_eval
的自定义字节码解释器,因此非常快速。
插件信息
插件名称 | 版本信息 |
---|---|
dart_eval | |
flutter_eval | |
eval_annotation |
实时示例
访问 EvalPad 查看实时示例。
注意事项
虽然 flutter_eval
支持大多数 Flutter 和 Dart 功能,但并非所有功能都受支持。现有的代码可能需要修改才能正常工作。您可以查看 这里 列出的支持的小部件和类,以及在 dart_eval Pub 页面 上列出的受支持的 Dart 特性。
快速入门:代码推送
首先,运行以下命令安装插件:
flutter pub add flutter_eval
在应用根目录下添加一个 HotSwapLoader
小部件,其中包含指向您将托管更新文件的位置的 URI:
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return HotSwapLoader(
uri: 'https://mysite.com/app_update/version_xxx.evc',
child: MaterialApp(
...
),
);
}
}
然后,在应用中添加 HotSwap
小部件以在每个位置动态更新:
class MyHomePage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return HotSwap(
id: '#myHomePage',
args: [BuildContext.wrap(context)],
childBuilder: (context) => Scaffold(
...
),
);
}
}
接下来,创建一个新的 Flutter 包,使用 flutter create --template=package
命令。这可以在您的应用文件夹内或单独创建。命名为适当的名称,例如 my_app_hot_update
。我们将从现在起将其称为“热更新包”。
转到 flutter_eval
的 发布页面,找到与您使用的 flutter_eval
版本对应的发布版本。在 Assets 下,下载 flutter_eval.json
。(或者点击 这里 下载最新版本。)
在热更新包的根目录下,创建一个名为 .dart_eval
的文件夹和一个子文件夹 bindings
。将下载的 flutter_eval.json
文件放入此文件夹中。
您的项目结构应该如下所示:
├── .dart_eval
│ └── bindings
│ └── flutter_eval.json.
├── pubspec.yaml
└── lib
└── hot_update.dart
在热更新包的根目录下运行以下命令:
flutter pub add eval_annotation
删除 main.dart
中的所有代码,包括 main()
函数。对于您希望更新的每个 HotSwap
小部件(可以更新全部、部分或仅一个),创建一个带有 @RuntimeOverride
注解的顶级函数,引用其 ID:
@RuntimeOverride('#myHomePage')
Widget myHomePageUpdate(BuildContext context) {
return Scaffold(
...
)
}
最后,我们需要安装 dart_eval
CLI:
dart pub global activate dart_eval
安装后,可以运行以下命令:
dart_eval compile -o version_xxx.evc
如果步骤正确执行,您应该会在控制台输出中看到以下内容:
Found binding file: .dart_eval\bindings\flutter_eval.json
以及
Compiled $x characters Dart to $y bytes EVC in $z ms: version_xxx.evc
生成的 version_xxx.evc
文件将在项目的根目录中,现在您可以将其上传到之前提到的服务器路径。
如果您运行该应用,您应该会看到 HotSwap
小部件的内容被替换为其相应 @RuntimeOverride
函数的内容。
HotSwap
小部件可以选择指定一种策略来加载/应用更新,即 immediate
、cache
或 cacheApplyOnRestart
。默认情况下,HotSwapLoader
在调试/配置模式下使用 immediate
,在发布模式下使用 cacheApplyOnRestart
。您还可以通过 loading
参数指定加载缓存时要显示的占位小部件。
快速入门:动态执行和服务器驱动的UI
要创建一个简单的动态无状态小部件,可以在 build()
方法内部或作为子参数添加并修改以下内容:
return EvalWidget(packages: {
'example': {
'main.dart': '''
import 'package:flutter/material.dart';
class MyWidget extends StatelessWidget {
MyWidget(this.name);
final String name;
[@override](/user/override)
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(5.0),
child: Column(children: [
Container(
color: Colors.red,
child: Text('The name is ' + name)
)
],
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
)
);
}
}''',
assetPath: 'assets/program.evc',
library: 'package:example/main.dart',
function: 'MyWidget.',
args: [String('Example name')]
}
});
内部地,EvalWidget
会自动切换两种模式:
- 在 调试模式 下,它将动态编译提供的 Dart 代码为 EVC 字节码,保存到由
assetPath
确定的文件,并在dart_eval
VM 中运行。这较慢,但允许您在开发过程中进行热重启。 - 在 发布模式 下,它将忽略提供的 Dart 代码,尝试从
assetPath
或由可选的uri
参数指定的自定义文件、资产或网络 URL 加载 EVC 字节码。这在运行时提供了高性能,同时仍然允许您通过提供新的 EVC 文件或 URL 来动态交换代码。
如果您从网络加载 EVC 字节码,可以使用 loading
参数指定可选的加载小部件。
flutter_eval
包含两个其他辅助小部件,用于不同的用例:
CompilerWidget
将始终编译并运行提供的 Dart 代码,无论应用程序是在调试还是发布模式下都不会尝试加载 EVC 字节码。这特别适合于计算器、学习编码平台或用户脚本化自动化工具等应用。尽管这种方式较慢,但dart_eval
编译器非常快,通常会在约 0.1 秒内编译简单的 Flutter 程序。RuntimeWidget
将始终加载 EVC 字节码,并不提供指定 Dart 代码的参数。如果使用 CLI 编译 - 请参见下方。
调用函数和传递参数
要使用默认构造函数实例化类,请在类名后面附加一个点号 .
。
当外部调用 dart_eval
函数或构造函数时,必须按顺序指定所有参数,即使这些参数是可选或命名的参数。使用 null
表示缺少参数(而 $null()
表示空值)。
例如,对于以下类:
class MyApp extends SomeWidget {
MyApp(this.name, {Key? key, Color? color}) : super(key: key, color: color);
final String name;
}
您可以在 RuntimeWidget
中使用以下方式实例化它:
return RuntimeWidget(
uri: Uri.parse('asset:assets/program.evc'),
library: 'package:example/main.dart',
function: 'MyApp.',
args: [String('Jessica'), null, null]
);
您也可以使用 $Function
传递回调。
安全性和权限
flutter_eval
继承了 dart_eval
的安全执行模型,该模型默认限制对文件系统、网络和其他敏感 API 的访问。flutter_eval
还扩展了对 MethodChannel
及相关 API 的安全控制。要允许访问 MethodChannel
,请在 EvalWidget
、CompilerWidget
或 RuntimeWidget
的 permissions
参数中添加 MethodChannelPermission
。
支持的小部件和类
目前支持的小部件和类包括:
- Widget, StatelessWidget, StatefulWidget, State;
- ChangeNotifier, Key, BuildContext;
- WidgetsApp, Container, Column, Row, Center;
- Alignment, Align, AspectRatio, Baseline;
- Radius, BorderRadius, BorderRadiusGeometry, Clip, ClipRRect;
- SizedBox, FittedBox, FractionallySizedBox, ColoredBox;
- Stack, Positioned, StackFit;
- Padding, EdgeInsetsGeometry, EdgeInsets, Axis, Size;
- Offset, Velocity;
- MainAxisAlignment, MainAxisSize, CrossAxisAlignment;
- AlignmentGeometry, Alignment, Constraints, BoxConstraints;
- Color, ColorSwatch, Colors, FontWeight, FontStyle;
- MaterialApp, MaterialColor, MaterialAccentColor;
- Theme, ThemeData, TextTheme, Directionality;
- Decoration, BoxDecoration, BoxBorder, Border, BorderSide;
- IconData, Icons, Icon;
- Curve, Curves, SawTooth, Interval, Threshold, Cubic;
- Text, TextStyle, TextEditingController, TextField;
- TextDirection, VerticalDirection, TextBaseline
- Scaffold, ScaffoldMessenger, AppBar, SnackBar, FloatingActionButton;
- InkWell, TextButton, ElevatedButton, IconButton;
- Card, Drawer, ListView, ListTile, Spacer;
- Image, ImageProvider, NetworkImage, MemoryImage;
- Navigator, NavigatorState, Builder;
- PointerDeviceKind, HitTestBehavior;
- GestureDetector, TapDownDetails, TapUpDetails;
- LongPressStartDetails, LongPressMoveUpdateDetails, LongPressEndDetails;
- DragStartDetails, DragUpdateDetails, DragEndDetails, DragDownDetails;
请注意,其中一些仅具有部分支持。
应用大小测量
应用 | 初始 APK 大小 | 使用 EvalWidget 后的大小 |
---|---|---|
Flutter Counter | 16.5 MB | 17.9 MB (+ 1.4 MB) |
Flutter Gallery | 110.2 MB | 110.6 MB (+ 0.4 MB) |
这些测量数据是在 flutter_eval v0.4.5 更新后的。它们不包括 EVC 字节码文件的大小,通常为 20-100KB(或压缩后 6-30KB),并且可能在安装后从服务器下载而不是与应用一起打包。
请注意,这些测量数据是为包含多个架构的生成组合 APK。从 Play 商店下载的 APK 大小大约是其一半,并且增加量也会减少。
高级用法
使用 flutter_eval
需要两个主要步骤:将 Dart 代码编译为 EVC 字节码,以及执行生成的 EVC 字节码。由于您不能期望所有现有的 Flutter/Dart 代码都能与 flutter_eval
兼容,建议在开发期间运行这两个步骤:
class ExampleState extends State<Example> {
late Runtime runtime;
[@override](/user/override)
void initState() {
super.initState();
final compiler = Compiler();
compiler.addPlugin(flutterEvalPlugin);
final program = compiler.compile({
'example': { 'main.dart': '''
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
HomePage(this.number);
final int number;
[@override](/user/override)
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(2.3 * 5),
child: Container(
color: Colors.green,
child: Text('Current amount: ' + number.toString())
)
);
}
}
''' }
});
final file = File('out.evc');
file.writeAsBytesSync(program.write());
print('Wrote out.evc to: ' + file.absolute.uri);
runtime = Runtime.ofProgram(program);
runtime.addPlugin(flutterEvalPlugin);
}
[@override](/user/override)
Widget build(BuildContext context) {
return (runtime.executeLib('package:example/main.dart', 'HomePage.', [int(55)]) as Value).$value;
}
}
有了这个设置,您可以快速查看代码中的任何错误。但是,我们也持续将 EVC 字节码写入文件 out.evc
。此文件包含从 Dart 源生成的编译字节码,当发布应用到生产环境时,这就是您所需的一切。在生产环境中运行编译器可能会导致应用运行速度变慢,因此强烈建议在生成后使用输出。EVC 字节码是平台无关的,因此您可以使用 Flutter 桌面生成 out.evc
文件并在 Flutter 移动应用中使用它。
生成后,您可以像这样在应用中使用它:
import 'package:flutter/services.dart' show rootBundle;
class ExampleState extends State<Example> {
Runtime? runtime;
[@override](/user/override)
void initState() {
super.initState();
rootBundle.load('assets/out.evc').then((bytecode) => setState(() {
runtime = Runtime(ByteData.sublistView(bytecode));
runtime.addPlugin(flutterEvalPlugin);
}));
}
[@override](/user/override)
Widget build(BuildContext context) {
if (runtime == null) return CircularProgressIndicator();
return (runtime.executeLib('package:example/main.dart', 'HomePage.', [int(55)]) as Value).$value;
}
}
更多关于Flutter动态表达式求值插件flutter_eval的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter动态表达式求值插件flutter_eval的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用flutter_eval
插件来进行动态表达式求值的示例代码。这个插件允许你在运行时解析和执行数学表达式。
1. 添加依赖
首先,你需要在pubspec.yaml
文件中添加flutter_eval
依赖:
dependencies:
flutter:
sdk: flutter
flutter_eval: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
2. 导入插件
在你的Dart文件中导入flutter_eval
插件:
import 'package:flutter/material.dart';
import 'package:flutter_eval/flutter_eval.dart';
3. 使用插件进行表达式求值
以下是一个简单的示例,展示如何使用flutter_eval
来求值一个简单的数学表达式:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Eval Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: EvalDemo(),
);
}
}
class EvalDemo extends StatefulWidget {
@override
_EvalDemoState createState() => _EvalDemoState();
}
class _EvalDemoState extends State<EvalDemo> {
final TextEditingController _controller = TextEditingController();
String _result = '';
void _evaluateExpression() {
final expression = _controller.text;
try {
final context = EvalContext();
final result = context.eval(expression);
setState(() {
_result = result.toString();
});
} catch (e) {
setState(() {
_result = 'Error: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Eval Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter Expression',
),
keyboardType: TextInputType.multiline,
maxLines: 5,
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _evaluateExpression,
child: Text('Evaluate'),
),
SizedBox(height: 16),
Text(
'Result: $_result',
style: TextStyle(fontSize: 20),
),
],
),
),
);
}
}
解释
- 添加依赖:在
pubspec.yaml
中添加flutter_eval
依赖。 - 导入插件:在需要使用
flutter_eval
的Dart文件中导入它。 - 构建UI:创建一个简单的UI,包含一个文本字段用于输入表达式,一个按钮用于触发求值操作,以及一个文本标签用于显示结果。
- 求值函数:在按钮的点击事件处理函数中,使用
EvalContext
来解析和执行输入的表达式。如果表达式有效,将结果显示在文本标签中;如果无效,则显示错误信息。
注意事项
flutter_eval
支持多种数学函数和操作符,但请确保输入的表达式符合其语法规则。- 在生产环境中,务必对用户输入进行验证,以防止潜在的安全问题(如代码注入)。
希望这个示例对你有所帮助!如果有其他问题,请随时提问。