Add @widgetFactory
annotation (#117455)
* Add `@widgetFactory` annotation * Simplify and fix example * Specify `TargetKind`s for `widgetFactory` * Explain why `library_private_types_in_public_api` is ignored. * Trigger CI
This commit is contained in:
parent
c6b636fa51
commit
2b7d709fdc
@ -23,6 +23,7 @@ import 'dart:ui' as ui
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:meta/meta_meta.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
@ -3684,3 +3685,68 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Target(<TargetKind>{TargetKind.method})
|
||||||
|
class _WidgetFactory {
|
||||||
|
const _WidgetFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Annotation which marks a function as a widget factory for the purpose of
|
||||||
|
/// widget creation tracking.
|
||||||
|
///
|
||||||
|
/// When widget creation tracking is enabled, the framework tracks the source
|
||||||
|
/// code location of the constructor call for each widget instance. This
|
||||||
|
/// information is used by the DevTools to provide an improved developer
|
||||||
|
/// experience. For example, it allows the Flutter inspector to present the
|
||||||
|
/// widget tree in a manner similar to how the UI was defined in your source
|
||||||
|
/// code.
|
||||||
|
///
|
||||||
|
/// [Widget] constructors are automatically instrumented to track the source
|
||||||
|
/// code location of constructor calls. However, there are cases where
|
||||||
|
/// a function acts as a sort of a constructor for a widget and a call to such
|
||||||
|
/// a function should be considered as the creation location for the returned
|
||||||
|
/// widget instance.
|
||||||
|
///
|
||||||
|
/// Annotating a function with this annotation marks the function as a widget
|
||||||
|
/// factory. The framework will then instrument that function in the same way
|
||||||
|
/// as it does for [Widget] constructors.
|
||||||
|
///
|
||||||
|
/// Note that the function **must not** have optional positional parameters for
|
||||||
|
/// tracking to work correctly.
|
||||||
|
///
|
||||||
|
/// Currently this annotation is only supported on extension methods.
|
||||||
|
///
|
||||||
|
/// {@tool snippet}
|
||||||
|
///
|
||||||
|
/// This example shows how to use the [widgetFactory] annotation to mark an
|
||||||
|
/// extension method as a widget factory:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// extension PaddingModifier on Widget {
|
||||||
|
/// @widgetFactory
|
||||||
|
/// Widget padding(EdgeInsetsGeometry padding) {
|
||||||
|
/// return Padding(padding: padding, child: this);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// When using the above extension method, the framework will track the
|
||||||
|
/// creation location of the [Padding] widget instance as the source code
|
||||||
|
/// location where the `padding` extension method was called:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// // continuing from previous example...
|
||||||
|
/// const Text('Hello World!')
|
||||||
|
/// .padding(const EdgeInsets.all(8));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * the documentation for [Track widget creation](https://docs.flutter.dev/development/tools/devtools/inspector#track-widget-creation).
|
||||||
|
// The below ignore is needed because the static type of the annotation is used
|
||||||
|
// by the CFE kernel transformer that implements the instrumentation to
|
||||||
|
// recognize the annotation.
|
||||||
|
// ignore: library_private_types_in_public_api
|
||||||
|
const _WidgetFactory widgetFactory = _WidgetFactory();
|
||||||
|
@ -238,6 +238,13 @@ int getChildLayerCount(OffsetLayer layer) {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TextFromString on String {
|
||||||
|
@widgetFactory
|
||||||
|
Widget text() {
|
||||||
|
return Text(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
_TestWidgetInspectorService.runTests();
|
_TestWidgetInspectorService.runTests();
|
||||||
}
|
}
|
||||||
@ -944,19 +951,20 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
|
|
||||||
testWidgets('WidgetInspectorService creationLocation', (WidgetTester tester) async {
|
testWidgets('WidgetInspectorService creationLocation', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
const Directionality(
|
Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text('a'),
|
const Text('a'),
|
||||||
Text('b', textDirection: TextDirection.ltr),
|
const Text('b', textDirection: TextDirection.ltr),
|
||||||
Text('c', textDirection: TextDirection.ltr),
|
'c'.text(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final Element elementA = find.text('a').evaluate().first;
|
final Element elementA = find.text('a').evaluate().first;
|
||||||
final Element elementB = find.text('b').evaluate().first;
|
final Element elementB = find.text('b').evaluate().first;
|
||||||
|
final Element elementC = find.text('c').evaluate().first;
|
||||||
|
|
||||||
service.disposeAllGroups();
|
service.disposeAllGroups();
|
||||||
service.resetPubRootDirectories();
|
service.resetPubRootDirectories();
|
||||||
@ -979,14 +987,28 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
final int columnB = creationLocationB['column']! as int;
|
final int columnB = creationLocationB['column']! as int;
|
||||||
final String? nameB = creationLocationB['name'] as String?;
|
final String? nameB = creationLocationB['name'] as String?;
|
||||||
expect(nameB, equals('Text'));
|
expect(nameB, equals('Text'));
|
||||||
|
|
||||||
|
service.setSelection(elementC, 'my-group');
|
||||||
|
final Map<String, Object?> jsonC = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
|
||||||
|
final Map<String, Object?> creationLocationC = jsonC['creationLocation']! as Map<String, Object?>;
|
||||||
|
expect(creationLocationC, isNotNull);
|
||||||
|
final String fileC = creationLocationC['file']! as String;
|
||||||
|
final int lineC = creationLocationC['line']! as int;
|
||||||
|
final int columnC = creationLocationC['column']! as int;
|
||||||
|
final String? nameC = creationLocationC['name'] as String?;
|
||||||
|
expect(nameC, equals('TextFromString|text'));
|
||||||
|
|
||||||
expect(fileA, endsWith('widget_inspector_test.dart'));
|
expect(fileA, endsWith('widget_inspector_test.dart'));
|
||||||
expect(fileA, equals(fileB));
|
expect(fileA, equals(fileB));
|
||||||
|
expect(fileA, equals(fileC));
|
||||||
// We don't hardcode the actual lines the widgets are created on as that
|
// We don't hardcode the actual lines the widgets are created on as that
|
||||||
// would make this test fragile.
|
// would make this test fragile.
|
||||||
expect(lineA + 1, equals(lineB));
|
expect(lineA + 1, equals(lineB));
|
||||||
|
expect(lineB + 1, equals(lineC));
|
||||||
// Column numbers are more stable than line numbers.
|
// Column numbers are more stable than line numbers.
|
||||||
expect(columnA, equals(15));
|
expect(columnA, equals(21));
|
||||||
expect(columnA, equals(columnB));
|
expect(columnA, equals(columnB));
|
||||||
|
expect(columnC, equals(19));
|
||||||
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.
|
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.
|
||||||
|
|
||||||
testWidgets('WidgetInspectorService setSelection notifiers for an Element',
|
testWidgets('WidgetInspectorService setSelection notifiers for an Element',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user