Skip to content

Commit b018ee5

Browse files
authored
Merge pull request #445 from AhmedLSayed9/improve_docs_and_assertion
Improve docs and validate selectedItemBuilder length
2 parents 4d781dd + f8a1891 commit b018ee5

9 files changed

Lines changed: 77 additions & 36 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
## Intro
1717

18-
Flutter's core Dropdown Button widget with steady dropdown menu and many other options you can
18+
Flutter's core Dropdown Button widget with a steady dropdown menu and many other options you can
1919
customize to your needs.
2020

2121
<img src="https://raw.githubusercontent.com/AhmedLSayed9/dropdown_button2/master/.github/images/banner.jpg" alt="Image" width="700"/>
@@ -68,7 +68,7 @@ customize to your needs.
6868
| Option | Description | Type | Required |
6969
| -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------- | :------: |
7070
| [items](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/items.html) | The list of items the user can select | List<DropdownItem<T>> | Yes |
71-
| [selectedItemBuilder](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/selectedItemBuilder.html) | A builder to customize how the selected item will be displayed on the button | DropdownButtonBuilder | No |
71+
| [selectedItemBuilder](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/selectedItemBuilder.html) | A builder to customize how the selected item will be displayed on the button | DropdownButton2Builder | No |
7272
| [valueListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/valueListenable.html) | A [ValueListenable] that represents the value of the currently selected [DropdownItem]. | ValueListenable<T?>? | No |
7373
| [multiValueListenable](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/multiValueListenable.html) | A [ValueListenable] that represents a list of the currently selected [DropdownItem]s | ValueListenable<List\<T>>? | No |
7474
| [hint](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/hint.html) | The placeholder displayed before the user choose an item | Widget | No |
@@ -1079,7 +1079,7 @@ class CustomDropdownButton2 extends StatelessWidget {
10791079
final ValueListenable<String?>? valueListenable;
10801080
final List<String> dropdownItems;
10811081
final ValueChanged<String?>? onChanged;
1082-
final DropdownButtonBuilder? selectedItemBuilder;
1082+
final DropdownButton2Builder? selectedItemBuilder;
10831083
final Alignment? hintAlignment;
10841084
final Alignment? valueAlignment;
10851085
final double? buttonHeight, buttonWidth;

packages/dropdown_button2/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Add `barrierBlocksInteraction` to allow interaction with underlying widgets while the dropdown menu is open.
1111
- Properly dispose internal FocusNode when replaced by an external FocusNode.
1212
- Add `mouseCursor` to the dropdown button and MenuItemStyleData to customize the mouse cursor when hovering, closes #416.
13+
- Improve docs and validate selectedItemBuilder length.
1314

1415
## 3.0.0
1516

packages/dropdown_button2/example/custom_dropdown_button2.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class CustomDropdownButton2 extends StatelessWidget {
3737
final ValueListenable<String?>? valueListenable;
3838
final List<String> dropdownItems;
3939
final ValueChanged<String?>? onChanged;
40-
final DropdownButtonBuilder? selectedItemBuilder;
40+
final DropdownButton2Builder? selectedItemBuilder;
4141
final Alignment? hintAlignment;
4242
final Alignment? valueAlignment;
4343
final double? buttonHeight, buttonWidth;

packages/dropdown_button2/lib/src/dropdown_button2.dart

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ const EdgeInsets _kUnalignedButtonPadding = EdgeInsets.zero;
3434
/// A builder to customize dropdown buttons.
3535
///
3636
/// Used by [DropdownButton2.selectedItemBuilder].
37+
///
38+
/// The list of widgets returned by this builder must be exactly the same length
39+
/// as the [DropdownButton2.items] list.
3740
typedef DropdownButton2Builder = Iterable<Widget> Function(BuildContext context);
3841

3942
/// A builder to customize the selected menu item.
@@ -83,7 +86,7 @@ typedef SearchMatchFn<T> = bool Function(DropdownItem<T> item, String searchValu
8386
/// * <https://material.io/design/components/menus.html#dropdown-menu>
8487
class DropdownButton2<T> extends StatefulWidget {
8588
/// Creates a DropdownButton2.
86-
/// It's customizable DropdownButton with steady dropdown menu and many other features.
89+
/// It's a customizable dropdown button with a steady dropdown menu and many other features.
8790
///
8891
/// The [items] must have distinct values. If [valueListenable] isn't null then its value
8992
/// must be equal to one of the [DropdownItem] values. If [multiValueListenable] isn't null
@@ -192,14 +195,8 @@ class DropdownButton2<T> extends StatefulWidget {
192195
///
193196
/// When a [DropdownItem] is selected, the widget that will be displayed
194197
/// from the list corresponds to the [DropdownItem] of the same index
195-
/// in [items].
196-
///
197-
/// {@tool dartpad}
198-
/// This sample shows a [DropdownButton] with a button with [Text] that
199-
/// corresponds to but is unique from [DropdownItem].
200-
///
201-
/// ** See code in examples/api/lib/material/dropdown/dropdown_button.selected_item_builder.0.dart **
202-
/// {@end-tool}
198+
/// in [items]. The list of widgets returned by this builder must be exactly
199+
/// the same length as the [items] list.
203200
///
204201
/// If this callback is null, the [DropdownItem] from [items]
205202
/// that matches the selected [DropdownItem]'s value will be displayed.
@@ -263,13 +260,6 @@ class DropdownButton2<T> extends StatefulWidget {
263260
/// To use a separate text style for selected item when it's displayed within
264261
/// the dropdown button, consider using [selectedItemBuilder].
265262
///
266-
/// {@tool dartpad}
267-
/// This sample shows a `DropdownButton` with a dropdown button text style
268-
/// that is different than its menu items.
269-
///
270-
/// ** See code in examples/api/lib/material/dropdown/dropdown_button.style.0.dart **
271-
/// {@end-tool}
272-
///
273263
/// Defaults to the [TextTheme.titleMedium] value of the current
274264
/// [ThemeData.textTheme] of the current [Theme].
275265
final TextStyle? style;
@@ -450,8 +440,8 @@ class _DropdownButton2State<T> extends State<DropdownButton2<T>> with WidgetsBin
450440

451441
final GlobalKey<State<StatefulWidget>> _buttonRectKey = GlobalKey();
452442

453-
// Using ValueNotifier for the Rect of DropdownButton so the dropdown menu listen and
454-
// update its position if DropdownButton's position has changed, as when keyboard open.
443+
// Using ValueNotifier for the Rect of DropdownButton2 so the dropdown menu listen and
444+
// update its position if DropdownButton2's position has changed, as when keyboard open.
455445
final ValueNotifier<Rect?> _buttonRect = ValueNotifier<Rect?>(null);
456446

457447
// Ancestor scroll positions we listen to while the menu is open, so the menu
@@ -850,9 +840,19 @@ class _DropdownButton2State<T> extends State<DropdownButton2<T>> with WidgetsBin
850840
// We should explicitly type the items list to be a list of <Widget>,
851841
// otherwise, no explicit type adding items maybe trigger a crash/failure
852842
// when hint and selectedItemBuilder are provided.
853-
final buttonItems = widget.selectedItemBuilder == null
854-
? (widget.items != null ? List<Widget>.of(widget.items!) : <Widget>[])
855-
: List<Widget>.of(widget.selectedItemBuilder!(context));
843+
final List<Widget> buttonItems;
844+
if (widget.selectedItemBuilder != null) {
845+
final selectedItems = List<Widget>.of(widget.selectedItemBuilder!(context));
846+
assert(
847+
widget.items == null || selectedItems.length == widget.items!.length,
848+
'The selectedItemBuilder must return a list of widgets with the same length as the items list.\n'
849+
'Currently, selectedItemBuilder returns a list of length ${selectedItems.length}, '
850+
'but items has length ${widget.items!.length}.',
851+
);
852+
buttonItems = selectedItems;
853+
} else {
854+
buttonItems = widget.items != null ? List<Widget>.of(widget.items!) : <Widget>[];
855+
}
856856

857857
int? hintIndex;
858858
if (widget.hint != null || (!_enabled && widget.disabledHint != null)) {
@@ -1189,7 +1189,7 @@ class DropdownButtonFormField2<T> extends FormField<T> {
11891189
? errorBuilder(state.context, field.errorText!)
11901190
: null;
11911191
final String? errorText = error == null ? field.errorText : null;
1192-
// Clear the decoration hintText because DropdownButton has its own hint logic.
1192+
// Clear the decoration hintText because DropdownButton2 has its own hint logic.
11931193
final String? hintText = effectiveDecoration.hintText != null ? '' : null;
11941194

11951195
effectiveDecoration = effectiveDecoration.copyWith(

packages/dropdown_button2/lib/src/dropdown_menu_item.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class DropdownItem<T> extends _DropdownMenuItemContainer {
2525

2626
/// The value to return if the user selects this menu item.
2727
///
28-
/// Eventually returned in a call to [DropdownButton.onChanged].
28+
/// Eventually returned in a call to [DropdownButton2.onChanged].
2929
final T? value;
3030

3131
/// Whether or not a user can select this menu item.
@@ -63,8 +63,8 @@ class DropdownItem<T> extends _DropdownMenuItemContainer {
6363
}
6464

6565
// The container widget for a menu item created by a [DropdownButton2]. It
66-
// provides the default configuration for [DropdownMenuItem]s, as well as a
67-
// [DropdownButton]'s hint and disabledHint widgets.
66+
// provides the default configuration for [DropdownItem]s, as well as a
67+
// [DropdownButton2]'s hint and disabledHint widgets.
6868
class _DropdownMenuItemContainer extends StatelessWidget {
6969
/// Creates an item for a dropdown menu.
7070
///

packages/dropdown_button2/lib/src/dropdown_route.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,10 @@ class _DropdownRoutePageState<T> extends State<_DropdownRoutePage<T>> {
371371
void initState() {
372372
super.initState();
373373
// Computing the initialScrollOffset now, before the items have been laid
374-
// out. This only works if the item heights are effectively fixed, i.e. either
375-
// DropdownButton.itemHeight is specified or DropdownButton.itemHeight is null
376-
// and all of the items' intrinsic heights are less than _kMenuItemHeight.
377-
// Otherwise the initialScrollOffset is just a rough approximation based on
378-
// treating the items as if their heights were all equal to _kMenuItemHeight.
374+
// out. This is accurate only if no item sets [DropdownItem.intrinsicHeight]
375+
// to true. When an item uses an intrinsic height, the offset is just a
376+
// rough approximation that uses the declared [DropdownItem.height] as a
377+
// fallback, since the actual height can't be known until layout.
379378
final _MenuLimits menuLimits = widget.route.getMenuLimits(
380379
widget.buttonRect,
381380
widget.constraints.maxHeight,

packages/dropdown_button2/lib/src/utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ void _uniqueValueAssert<T>(
1010
}
1111

1212
String assertMessage(T value) {
13-
return "There should be exactly one item with [DropdownButton]'s value: "
13+
return "There should be exactly one item with [DropdownButton2]'s value: "
1414
'$value. \n'
1515
'Either zero or 2 or more [DropdownItem]s were detected '
1616
'with the same value';

packages/dropdown_button2/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: dropdown_button2
2-
description: Flutter's core Dropdown Button widget with steady dropdown menu and many options you can customize to your needs.
2+
description: Flutter's core Dropdown Button widget with a steady dropdown menu and many other options you can customize to your needs.
33
version: 3.0.0
44
repository: https://github.com/AhmedLSayed9/dropdown_button2
55
issue_tracker: https://github.com/AhmedLSayed9/dropdown_button2/issues

packages/dropdown_button2/test/dropdown_button2_test.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,4 +1053,45 @@ void main() {
10531053
},
10541054
);
10551055
});
1056+
1057+
group(
1058+
'Selected Item Builder',
1059+
() {
1060+
final menuItems = List<int>.generate(4, (int index) => index);
1061+
1062+
List<DropdownItem<int>> buildItems() {
1063+
return menuItems.map<DropdownItem<int>>((int item) {
1064+
return DropdownItem<int>(
1065+
value: item,
1066+
child: Text(item.toString()),
1067+
);
1068+
}).toList();
1069+
}
1070+
1071+
testWidgets(
1072+
'selectedItemBuilder should assert when it returns a different number of widgets than items',
1073+
(WidgetTester tester) async {
1074+
await tester.pumpWidget(
1075+
MaterialApp(
1076+
home: Scaffold(
1077+
body: DropdownButton2<int>(
1078+
items: buildItems(),
1079+
selectedItemBuilder: (BuildContext context) => const [Text('only one')],
1080+
onChanged: (_) {},
1081+
),
1082+
),
1083+
),
1084+
);
1085+
1086+
final Object? exception = tester.takeException();
1087+
expect(exception, isA<AssertionError>());
1088+
expect(
1089+
(exception! as AssertionError).message,
1090+
'The selectedItemBuilder must return a list of widgets with the same length as the items list.\n'
1091+
'Currently, selectedItemBuilder returns a list of length 1, but items has length 4.',
1092+
);
1093+
},
1094+
);
1095+
},
1096+
);
10561097
}

0 commit comments

Comments
 (0)