Actions API 修订
摘要
#在 Flutter 中,Intent 是一个对象,通常使用 Shortcuts 小部件绑定到键盘快捷键组合。Intent 可以绑定到 Action,后者可以更新应用程序的状态或执行其他操作。在使用此 API 的过程中,我们发现设计中存在一些缺点,因此我们更新了 Actions API,使其更易于使用和理解。
在之前的 Actions API 设计中,操作是从 LocalKey 映射到 ActionFactory,后者每次调用 invoke 方法时都会创建一个新的 Action。在当前的 API 中,操作是从 Intent 的类型映射到 Action 实例(使用 Map<Type, Action>),并且不会为每次调用都重新创建它们。
上下文
#最初的 Actions API 设计面向从窗口小部件调用操作,并让这些操作在窗口小部件的上下文中执行。团队一直在使用操作,并发现该设计中存在一些需要解决的限制:
操作无法从窗口小部件层次结构外部调用。这方面的例子包括处理命令脚本、一些撤销架构和一些控制器架构。
从快捷键到
Intent,然后到Action的映射并不总是很清晰,因为数据结构将 LogicalKeySet 映射到 Intent,然后将LocalKey映射到ActionFactory。新的映射仍然是LogicalKeySet到Intent,但随后它将Type(Intent类型)映射到Action,这更直接、更易读,因为 Intent 的类型写在映射中。如果操作的键绑定位于窗口小部件层次结构的另一部分,则
Intent并不总是能够访问决定是否应启用 intent/action 的必要状态。
为了解决这些问题,我们对 API 做了一些重大更改。操作的映射变得更直观,启用接口已移至 Action 类。从 Action 的 invoke 方法及其构造函数中删除了一些不必要的参数,并允许操作从其 invoke 方法返回结果。操作被制作成泛型,接受它们处理的 Intent 类型,不再使用 LocalKeys 来标识要运行的操作,而是使用 Intent 的类型。
这些更改中的大部分是在 修改 Action API 和 使 Action.enabled 成为 isEnabled(Intent intent) 而不是 的 PR 中完成的,并在设计文档 中进行了详细说明。
更改说明
#以下是为解决上述问题所做的更改:
- 提供给
Actions小部件的Map<LocalKey, ActionFactory>现在是Map<Type, Action<Intent>>(类型是要传递给 Action 的 Intent 的类型)。 isEnabled方法已从Intent类移动到Action类。- 删除了
Action.invoke和Actions.invoke方法的FocusNode参数。 - 调用操作不再创建
Action的新实例。 - 从
Intent构造函数中删除了LocalKey参数。 - 从
CallbackAction中删除了LocalKey参数。 Action类现在是泛型 (Action<T extends Intent>),以提高类型安全性。CallbackAction使用的OnInvokeCallback不再接受FocusNode参数。ActionDispatcher.invokeAction签名已更改为不接受可选的FocusNode,而是接受可选的BuildContext。- 删除了
Action子类中的LocalKey静态常量(按约定命名的键)。 Action.invoke和ActionDispatcher.invokeAction方法现在将调用操作的结果作为Object返回。- 现在可以监听
Action类的状态更改。 ActionFactorytypedef 已被删除,因为它不再使用。
示例分析器错误
#以下是一些示例分析器错误,这些错误可能是由于 Actions API 的过时使用造成的。错误的具体细节可能会有所不同,并且这些更改还可能导致其他错误。
error: MyActionDispatcher.invokeAction' ('bool Function(Action<Intent>, Intent, {FocusNode focusNode})') isn't a valid override of 'ActionDispatcher.invokeAction' ('Object Function(Action<Intent>, Intent, [BuildContext])'). (invalid_override at [main] lib/main.dart:74)
error: MyAction.invoke' ('void Function(FocusNode, Intent)') isn't a valid override of 'Action.invoke' ('Object Function(Intent)'). (invalid_override at [main] lib/main.dart:231)
error: The method 'isEnabled' isn't defined for the type 'Intent'. (undefined_method at [main] lib/main.dart:97)
error: The argument type 'Null Function(FocusNode, Intent)' can't be assigned to the parameter type 'Object Function(Intent)'. (argument_type_not_assignable at [main] lib/main.dart:176)
error: The getter 'key' isn't defined for the type 'NextFocusAction'. (undefined_getter at [main] lib/main.dart:294)
error: The argument type 'Map<LocalKey, dynamic>' can't be assigned to the parameter type 'Map<Type, Action<Intent>>'. (argument_type_not_assignable at [main] lib/main.dart:418)迁移指南
#需要进行重大更改才能将现有代码更新到新的 API。
预定义操作的操作映射
#要更新 Actions 小部件中 Flutter 预定义操作(如 ActivateAction 和 SelectAction)的操作映射,请执行以下操作:
- 更新
actions参数的参数类型 - 在
Shortcuts映射中使用特定Intent类的实例,而不是Intent(TheAction.key)实例。
迁移前的代码:
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): Intent(ActivateAction.key),
},
child: Actions(
actions: <LocalKey, ActionFactory>{
Activate.key: () => ActivateAction(),
},
child: Container(),
)
);
}
}迁移后的代码:
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): ActivateIntent,
},
child: Actions(
actions: <Type, Action<Intent>>{
ActivateIntent: ActivateAction(),
},
child: Container(),
)
);
}
}自定义操作
#要迁移自定义操作,请消除已定义的 LocalKeys,并将其替换为 Intent 子类,以及更改 Actions 小部件的 actions 参数的参数类型。
迁移前的代码:
class MyAction extends Action {
MyAction() : super(key);
/// 将此操作唯一标识给 [Intent] 的 [LocalKey]。
static const LocalKey key = ValueKey<Type>(RequestFocusAction);
@override
void invoke(FocusNode node, MyIntent intent) {
// ...
}
}
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): Intent(MyAction.key),
},
child: Actions(
actions: <LocalKey, ActionFactory>{
MyAction.key: () => MyAction(),
},
child: Container(),
)
);
}
}迁移后的代码:
// 如果之前使用的是裸 `LocalKey`,则可能需要创建新的 `Intent` 子类。
class MyIntent extends Intent {
const MyIntent();
}
class MyAction extends Action<MyIntent> {
@override
Object invoke(MyIntent intent) {
// ...
}
}
class MyWidget extends StatelessWidget {
// ...
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent> {
LogicalKeySet(LogicalKeyboardKey.enter): MyIntent,
},
child: Actions(
actions: <Type, Action<Intent>>{
MyIntent: MyAction(),
},
child: Container(),
)
);
}
}带参数的自定义 Actions 和 Intents
#要更新使用 intent 参数或保存状态的操作,需要修改 invoke 方法的参数。在下面的示例中,代码将参数的值保存在 intent 中,作为操作实例的一部分。这是因为在旧设计中,每次执行操作时都会创建一个新的实例,并且生成的 action 可以由 ActionDispatcher 保留以记录状态。
在下面的迁移后代码示例中,新的 MyAction 将状态作为调用 invoke 的结果返回,因为不会为每次调用创建新的实例。此状态将返回给 Actions.invoke 或 ActionDispatcher.invokeAction 的调用者,具体取决于操作的调用方式。
迁移前的代码:
class MyIntent extends Intent {
const MyIntent({this.argument});
final int argument;
}
class MyAction extends Action {
MyAction() : super(key);
/// 将此操作唯一标识给 [Intent] 的 [LocalKey]。
static const LocalKey key = ValueKey<Type>(RequestFocusAction);
int state;
@override
void invoke(FocusNode node, MyIntent intent) {
// ...
state = intent.argument;
}
}迁移后的代码:
class MyIntent extends Intent {
const MyIntent({this.argument});
final int argument;
}
class MyAction extends Action<MyIntent> {
@override
int invoke(Intent intent) {
// ...
return intent.argument;
}
}时间线
#包含在版本中:1.18
稳定版本中:1.20
参考
#API 文档:
相关问题:
相关 PR:
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。