Actions API 修订
概述
#在 Flutter 中,Intent
是一个对象,通常使用 Shortcuts
控件绑定到键盘组合键。一个 Intent
可以绑定到一个 Action
,Action
可以更新应用程序状态或执行其他操作。在使用此 API 的过程中,我们发现了设计中的几个缺点,因此我们更新了 Actions API,使其更易于使用和理解。
在之前的 Actions API 设计中,操作是从 LocalKey
映射到一个 ActionFactory
,每次调用 invoke
方法时都会创建一个新的 Action
。在当前的 API 中,操作是从 Intent
的类型映射到一个 Action
实例(使用 Map<Type, Action>
),并且每次调用都不会重新创建。
背景
#最初的 Actions API 设计旨在从控件调用操作,并让这些操作在控件的上下文中执行。团队在使用 Actions 时发现了该设计中的几个限制,需要加以解决。
无法从控件层级结构之外调用 Actions。这方面的例子包括处理命令脚本、一些撤销架构和一些控制器架构。
从快捷键到
Intent
再到Action
的映射并不总是清晰的,因为数据结构映射的是 LogicalKeySet =>Intent,然后是LocalKey
=>ActionFactory
。新的映射仍然是LogicalKeySet
到Intent
,但之后它将Type
(Intent
类型) 映射到Action
,这更直接和可读,因为 Intent 的类型写在了映射中。如果操作的键绑定位于控件层级结构的其他部分,
Intent
并非总能访问到判断 Intent/Action 是否应启用所需的 states。
为了解决这些问题,我们对 API 进行了重大更改。操作的映射变得更加直观,并且 enabled 接口已移至 Action
类。Action
的 invoke
方法及其构造函数中删除了一些不必要的参数,并且允许 Actions 从其 invoke
方法返回结果。Actions 被设计为泛型,接受它们处理的 Intent
类型,并且不再使用 LocalKeys
来标识要运行哪个 Action,而是使用 Intent
的类型。
这些更改大部分是在 Revise Action API 和 Make Action.enabled be isEnabled(Intent intent) instead 的 PR 中进行的,并在 设计文档 中详细描述。
变更说明
#以下是为解决上述问题所做的更改
- 传递给
Actions
控件的Map<LocalKey, ActionFactory>
现在是Map<Type, Action<Intent>>
(该类型是要传递给 Action 的 Intent 的类型)。 isEnabled
方法已从Intent
类移动到Action
类。FocusNode
参数已从Action.invoke
和Actions.invoke
方法中移除。- 调用 Action 不再创建
Action
的新实例。 LocalKey
参数已从Intent
构造函数中移除。LocalKey
参数已从CallbackAction
中移除。Action
类现在是泛型 (Action<T extends Intent>
),以提高类型安全性。CallbackAction
使用的OnInvokeCallback
不再接受FocusNode
参数。ActionDispatcher.invokeAction
签名已更改,不再接受可选的FocusNode
,而是接受可选的BuildContext
。Action
子类中的LocalKey
静态常量(按照惯例命名为 key)已被移除。Action.invoke
和ActionDispatcher.invokeAction
方法现在将调用操作的结果作为Object
返回。Action
类现在可以监听状态更改。ActionFactory
typedef 已被移除,因为它不再使用。
分析器故障示例
#以下是一些可能遇到的分析器故障示例,这些故障可能是由于 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
控件中的操作映射,请执行以下操作:
- 更新
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(),
)
);
}
}
自定义 Actions
#要迁移您的自定义 Actions,请移除您已定义的 LocalKeys
,并将其替换为 Intent
子类,同时更改 Actions
控件的 actions
参数的参数类型。
迁移前的代码
class MyAction extends Action {
MyAction() : super(key);
/// The [LocalKey] that uniquely identifies this action to an [Intent].
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(),
)
);
}
}
迁移后的代码
// You may need to create new Intent subclasses if you used
// a bare LocalKey before.
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 参数或持有状态的 Actions,您需要修改 invoke
方法的参数。在下面的示例中,代码将 Intent 中的参数值作为 Action 实例的一部分保留。这是因为在旧设计中,每次执行时都会创建一个新的 Action 实例,并且生成的 Action 可以由 ActionDispatcher
保存以记录状态。
在下面的迁移后代码示例中,新的 MyAction
通过调用 invoke
返回状态,因为每次调用都不会创建新的实例。此状态将返回给 Actions.invoke
或 ActionDispatcher.invokeAction
的调用方,具体取决于 Action 的调用方式。
迁移前的代码
class MyIntent extends Intent {
const MyIntent({this.argument});
final int argument;
}
class MyAction extends Action {
MyAction() : super(key);
/// The [LocalKey] that uniquely identifies this action to an [Intent].
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