Skip to content

Commit ca75e66

Browse files
authored
Reland "Migrate android_semantics_testing to null safety (#111420)" (#111487)
1 parent 26a76fe commit ca75e66

File tree

10 files changed

+115
-96
lines changed

10 files changed

+115
-96
lines changed

dev/integration_tests/android_semantics_testing/lib/main.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ void main() {
2323

2424
const MethodChannel kSemanticsChannel = MethodChannel('semantics');
2525

26-
Future<String> dataHandler(String message) async {
27-
if (message.contains('getSemanticsNode')) {
26+
Future<String> dataHandler(String? message) async {
27+
if (message != null && message.contains('getSemanticsNode')) {
2828
final Completer<String> completer = Completer<String>();
2929
final int id = int.tryParse(message.split('#')[1]) ?? 0;
30-
Future<void> completeSemantics([Object _]) async {
30+
Future<void> completeSemantics([Object? _]) async {
3131
final dynamic result = await kSemanticsChannel.invokeMethod<dynamic>('getSemanticsNode', <String, dynamic>{
3232
'id': id,
3333
});
@@ -40,10 +40,10 @@ Future<String> dataHandler(String message) async {
4040
}
4141
return completer.future;
4242
}
43-
if (message.contains('setClipboard')) {
43+
if (message != null && message.contains('setClipboard')) {
4444
final Completer<String> completer = Completer<String>();
4545
final String str = message.split('#')[1];
46-
Future<void> completeSetClipboard([Object _]) async {
46+
Future<void> completeSetClipboard([Object? _]) async {
4747
await kSemanticsChannel.invokeMethod<dynamic>('setClipboard', <String, dynamic>{
4848
'message': str,
4949
});
@@ -67,7 +67,7 @@ Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
6767
};
6868

6969
class TestApp extends StatelessWidget {
70-
const TestApp({Key key}) : super(key: key);
70+
const TestApp({super.key});
7171

7272
@override
7373
Widget build(BuildContext context) {

dev/integration_tests/android_semantics_testing/lib/src/common.dart

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
// ignore_for_file: avoid_dynamic_calls
6+
57
import 'dart:convert';
68

79
import 'package:meta/meta.dart';
@@ -53,19 +55,19 @@ class AndroidSemanticsNode {
5355
/// ]
5456
/// }
5557
factory AndroidSemanticsNode.deserialize(String value) {
56-
return AndroidSemanticsNode._(json.decode(value) as Map<String, Object>);
58+
return AndroidSemanticsNode._(json.decode(value));
5759
}
5860

59-
final Map<String, Object> _values;
61+
final dynamic _values;
6062
final List<AndroidSemanticsNode> _children = <AndroidSemanticsNode>[];
6163

62-
Map<String, Object> get _flags => _values['flags'] as Map<String, Object>;
64+
dynamic get _flags => _values['flags'];
6365

6466
/// The text value of the semantics node.
6567
///
6668
/// This is produced by combining the value, label, and hint fields from
6769
/// the Flutter [SemanticsNode].
68-
String get text => _values['text'] as String;
70+
String? get text => _values['text'] as String?;
6971

7072
/// The contentDescription of the semantics node.
7173
///
@@ -75,7 +77,7 @@ class AndroidSemanticsNode {
7577
///
7678
/// This is produced by combining the value, label, and hint fields from
7779
/// the Flutter [SemanticsNode].
78-
String get contentDescription => _values['contentDescription'] as String;
80+
String? get contentDescription => _values['contentDescription'] as String?;
7981

8082
/// The className of the semantics node.
8183
///
@@ -84,61 +86,63 @@ class AndroidSemanticsNode {
8486
///
8587
/// If a more specific value isn't provided, it defaults to
8688
/// "android.view.View".
87-
String get className => _values['className'] as String;
89+
String? get className => _values['className'] as String?;
8890

8991
/// The identifier for this semantics node.
90-
int get id => _values['id'] as int;
92+
int? get id => _values['id'] as int?;
9193

9294
/// The children of this semantics node.
9395
List<AndroidSemanticsNode> get children => _children;
9496

9597
/// Whether the node is currently in a checked state.
9698
///
9799
/// Equivalent to [SemanticsFlag.isChecked].
98-
bool get isChecked => _flags['isChecked'] as bool;
100+
bool? get isChecked => _flags['isChecked'] as bool?;
99101

100102
/// Whether the node can be in a checked state.
101103
///
102104
/// Equivalent to [SemanticsFlag.hasCheckedState]
103-
bool get isCheckable => _flags['isCheckable'] as bool;
105+
bool? get isCheckable => _flags['isCheckable'] as bool?;
104106

105107
/// Whether the node is editable.
106108
///
107109
/// This is usually only applied to text fields, which map
108110
/// to "android.widget.EditText".
109-
bool get isEditable => _flags['isEditable'] as bool;
111+
bool? get isEditable => _flags['isEditable'] as bool?;
110112

111113
/// Whether the node is enabled.
112-
bool get isEnabled => _flags['isEnabled'] as bool;
114+
bool? get isEnabled => _flags['isEnabled'] as bool?;
113115

114116
/// Whether the node is focusable.
115-
bool get isFocusable => _flags['isFocusable'] as bool;
117+
bool? get isFocusable => _flags['isFocusable'] as bool?;
116118

117119
/// Whether the node is focused.
118-
bool get isFocused => _flags['isFocused'] as bool;
120+
bool? get isFocused => _flags['isFocused'] as bool?;
119121

120122
/// Whether the node is considered a heading.
121-
bool get isHeading => _flags['isHeading'] as bool;
123+
bool? get isHeading => _flags['isHeading'] as bool?;
122124

123125
/// Whether the node represents a password field.
124126
///
125127
/// Equivalent to [SemanticsFlag.isObscured].
126-
bool get isPassword => _flags['isPassword'] as bool;
128+
bool? get isPassword => _flags['isPassword'] as bool?;
127129

128130
/// Whether the node is long clickable.
129131
///
130132
/// Equivalent to having [SemanticsAction.longPress].
131-
bool get isLongClickable => _flags['isLongClickable'] as bool;
133+
bool? get isLongClickable => _flags['isLongClickable'] as bool?;
132134

133135
/// Gets a [Rect] which defines the position and size of the semantics node.
134136
Rect getRect() {
135-
final Map<String, Object> rawRect = _values['rect'] as Map<String, Object>;
136-
final Map<String, int> rect = rawRect.cast<String, int>();
137+
final dynamic rawRect = _values['rect'];
138+
if (rawRect == null) {
139+
return const Rect.fromLTRB(0.0, 0.0, 0.0, 0.0);
140+
}
137141
return Rect.fromLTRB(
138-
rect['left'].toDouble(),
139-
rect['top'].toDouble(),
140-
rect['right'].toDouble(),
141-
rect['bottom'].toDouble(),
142+
(rawRect['left']! as int).toDouble(),
143+
(rawRect['top']! as int).toDouble(),
144+
(rawRect['right']! as int).toDouble(),
145+
(rawRect['bottom']! as int).toDouble(),
142146
);
143147
}
144148

@@ -149,9 +153,20 @@ class AndroidSemanticsNode {
149153
}
150154

151155
/// Gets a list of [AndroidSemanticsActions] which are defined for the node.
152-
List<AndroidSemanticsAction> getActions() => <AndroidSemanticsAction>[
153-
for (final int id in (_values['actions'] as List<dynamic>).cast<int>()) AndroidSemanticsAction.deserialize(id),
154-
];
156+
List<AndroidSemanticsAction> getActions() {
157+
final List<int>? actions = (_values['actions'] as List<dynamic>?)?.cast<int>();
158+
if (actions == null) {
159+
return const <AndroidSemanticsAction>[];
160+
}
161+
final List<AndroidSemanticsAction> convertedActions = <AndroidSemanticsAction>[];
162+
for (final int id in actions) {
163+
final AndroidSemanticsAction? action = AndroidSemanticsAction.deserialize(id);
164+
if (action != null) {
165+
convertedActions.add(action);
166+
}
167+
}
168+
return convertedActions;
169+
}
155170

156171
@override
157172
String toString() {

dev/integration_tests/android_semantics_testing/lib/src/constants.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class AndroidSemanticsAction {
168168
case _kSetText:
169169
return 'AndroidSemanticsAction.setText';
170170
default:
171-
return null;
171+
throw UnimplementedError();
172172
}
173173
}
174174

@@ -211,7 +211,7 @@ class AndroidSemanticsAction {
211211
/// Creates a new [AndroidSemanticsAction] from an integer `value`.
212212
///
213213
/// Returns `null` if the id is not a known Android accessibility action.
214-
static AndroidSemanticsAction deserialize(int value) {
214+
static AndroidSemanticsAction? deserialize(int value) {
215215
return _kActionById[value];
216216
}
217217
}

dev/integration_tests/android_semantics_testing/lib/src/matcher.dart

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,24 @@ import 'constants.dart';
1818
/// the Android accessibility bridge, and not the semantics object created by
1919
/// the Flutter framework.
2020
Matcher hasAndroidSemantics({
21-
String text,
22-
String contentDescription,
23-
String className,
24-
int id,
25-
Rect rect,
26-
Size size,
27-
List<AndroidSemanticsAction> actions,
28-
List<AndroidSemanticsAction> ignoredActions,
29-
List<AndroidSemanticsNode> children,
30-
bool isChecked,
31-
bool isCheckable,
32-
bool isEditable,
33-
bool isEnabled,
34-
bool isFocusable,
35-
bool isFocused,
36-
bool isHeading,
37-
bool isPassword,
38-
bool isLongClickable,
21+
String? text,
22+
String? contentDescription,
23+
String? className,
24+
int? id,
25+
Rect? rect,
26+
Size? size,
27+
List<AndroidSemanticsAction>? actions,
28+
List<AndroidSemanticsAction>? ignoredActions,
29+
List<AndroidSemanticsNode>? children,
30+
bool? isChecked,
31+
bool? isCheckable,
32+
bool? isEditable,
33+
bool? isEnabled,
34+
bool? isFocusable,
35+
bool? isFocused,
36+
bool? isHeading,
37+
bool? isPassword,
38+
bool? isLongClickable,
3939
}) {
4040
return _AndroidSemanticsMatcher(
4141
text: text,
@@ -79,23 +79,23 @@ class _AndroidSemanticsMatcher extends Matcher {
7979
this.isLongClickable,
8080
});
8181

82-
final String text;
83-
final String className;
84-
final String contentDescription;
85-
final int id;
86-
final List<AndroidSemanticsAction> actions;
87-
final List<AndroidSemanticsAction> ignoredActions;
88-
final Rect rect;
89-
final Size size;
90-
final bool isChecked;
91-
final bool isCheckable;
92-
final bool isEditable;
93-
final bool isEnabled;
94-
final bool isFocusable;
95-
final bool isFocused;
96-
final bool isHeading;
97-
final bool isPassword;
98-
final bool isLongClickable;
82+
final String? text;
83+
final String? className;
84+
final String? contentDescription;
85+
final int? id;
86+
final List<AndroidSemanticsAction>? actions;
87+
final List<AndroidSemanticsAction>? ignoredActions;
88+
final Rect? rect;
89+
final Size? size;
90+
final bool? isChecked;
91+
final bool? isCheckable;
92+
final bool? isEditable;
93+
final bool? isEnabled;
94+
final bool? isFocusable;
95+
final bool? isFocused;
96+
final bool? isHeading;
97+
final bool? isPassword;
98+
final bool? isLongClickable;
9999

100100
@override
101101
Description describe(Description description) {
@@ -149,7 +149,7 @@ class _AndroidSemanticsMatcher extends Matcher {
149149
}
150150

151151
@override
152-
bool matches(covariant AndroidSemanticsNode item, Map<Object, Object> matchState) {
152+
bool matches(covariant AndroidSemanticsNode item, Map<dynamic, dynamic> matchState) {
153153
if (text != null && text != item.text) {
154154
return _failWithMessage('Expected text: $text', matchState);
155155
}
@@ -170,13 +170,13 @@ class _AndroidSemanticsMatcher extends Matcher {
170170
}
171171
if (actions != null) {
172172
final List<AndroidSemanticsAction> itemActions = item.getActions();
173-
if (!unorderedEquals(actions).matches(itemActions, matchState)) {
174-
final List<String> actionsString = actions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
173+
if (!unorderedEquals(actions!).matches(itemActions, matchState)) {
174+
final List<String> actionsString = actions!.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
175175
final List<String> itemActionsString = itemActions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
176-
final Set<AndroidSemanticsAction> unexpected = itemActions.toSet().difference(actions.toSet());
176+
final Set<AndroidSemanticsAction> unexpected = itemActions.toSet().difference(actions!.toSet());
177177
final Set<String> unexpectedInString = itemActionsString.toSet().difference(actionsString.toSet());
178178
final Set<String> missingInString = actionsString.toSet().difference(itemActionsString.toSet());
179-
if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions.contains)) {
179+
if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions!.contains)) {
180180
return true;
181181
}
182182
return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState);
@@ -214,9 +214,13 @@ class _AndroidSemanticsMatcher extends Matcher {
214214
}
215215

216216
@override
217-
Description describeMismatch(Object item, Description mismatchDescription,
218-
Map<Object, Object> matchState, bool verbose) {
219-
return mismatchDescription.add(matchState['failure'] as String);
217+
Description describeMismatch(dynamic item, Description mismatchDescription,
218+
Map<dynamic, dynamic> matchState, bool verbose) {
219+
final String? failure = matchState['failure'] as String?;
220+
if (failure == null) {
221+
return mismatchDescription.add('hasAndroidSemantics matcher does not complete successfully');
222+
}
223+
return mismatchDescription.add(failure);
220224
}
221225

222226
bool _failWithMessage(String value, Map<dynamic, dynamic> matchState) {

dev/integration_tests/android_semantics_testing/lib/src/tests/controls_page.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export 'controls_constants.dart';
99

1010
/// A test page with a checkbox, three radio buttons, and a switch.
1111
class SelectionControlsPage extends StatefulWidget {
12-
const SelectionControlsPage({Key key}) : super(key: key);
12+
const SelectionControlsPage({super.key});
1313

1414
@override
1515
State<StatefulWidget> createState() => _SelectionControlsPageState();
@@ -28,15 +28,15 @@ class _SelectionControlsPageState extends State<SelectionControlsPage> {
2828
bool _isLabeledOn = false;
2929
int _radio = 0;
3030

31-
void _updateCheckbox(bool newValue) {
31+
void _updateCheckbox(bool? newValue) {
3232
setState(() {
33-
_isChecked = newValue;
33+
_isChecked = newValue!;
3434
});
3535
}
3636

37-
void _updateRadio(int newValue) {
37+
void _updateRadio(int? newValue) {
3838
setState(() {
39-
_radio = newValue;
39+
_radio = newValue!;
4040
});
4141
}
4242

dev/integration_tests/android_semantics_testing/lib/src/tests/headings_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export 'headings_constants.dart';
99

1010
/// A test page with an app bar and some body text for testing heading flags.
1111
class HeadingsPage extends StatelessWidget {
12-
const HeadingsPage({Key key}) : super(key: key);
12+
const HeadingsPage({super.key});
1313

1414
static const ValueKey<String> _appBarTitleKey = ValueKey<String>(appBarTitleKeyValue);
1515
static const ValueKey<String> _bodyTextKey = ValueKey<String>(bodyTextKeyValue);

dev/integration_tests/android_semantics_testing/lib/src/tests/popup_page.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export 'popup_constants.dart';
1010

1111
/// A page with a popup menu, a dropdown menu, and a modal alert.
1212
class PopupControlsPage extends StatefulWidget {
13-
const PopupControlsPage({Key key}) : super(key: key);
13+
const PopupControlsPage({super.key});
1414

1515
@override
1616
State<StatefulWidget> createState() => _PopupControlsPageState();
@@ -60,9 +60,9 @@ class _PopupControlsPageState extends State<PopupControlsPage> {
6060
child: Text(item),
6161
);
6262
}).toList(),
63-
onChanged: (String value) {
63+
onChanged: (String? value) {
6464
setState(() {
65-
dropdownValue = value;
65+
dropdownValue = value!;
6666
});
6767
},
6868
),

dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export 'text_field_constants.dart';
1010

1111
/// A page with a normal text field and a password field.
1212
class TextFieldPage extends StatefulWidget {
13-
const TextFieldPage({Key key}) : super(key: key);
13+
const TextFieldPage({super.key});
1414

1515
@override
1616
State<StatefulWidget> createState() => _TextFieldPageState();

0 commit comments

Comments
 (0)