Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

## Intro

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

<img src="https://raw.githubusercontent.com/AhmedLSayed9/dropdown_button2/master/.github/images/banner.jpg" alt="Image" width="700"/>
Expand Down Expand Up @@ -68,7 +68,7 @@ customize to your needs.
| Option | Description | Type | Required |
| -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------- | :------: |
| [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 |
| [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 |
| [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 |
| [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 |
| [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 |
| [hint](https://pub.dev/documentation/dropdown_button2/latest/dropdown_button2/DropdownButton2/hint.html) | The placeholder displayed before the user choose an item | Widget | No |
Expand Down Expand Up @@ -1079,7 +1079,7 @@ class CustomDropdownButton2 extends StatelessWidget {
final ValueListenable<String?>? valueListenable;
final List<String> dropdownItems;
final ValueChanged<String?>? onChanged;
final DropdownButtonBuilder? selectedItemBuilder;
final DropdownButton2Builder? selectedItemBuilder;
final Alignment? hintAlignment;
final Alignment? valueAlignment;
final double? buttonHeight, buttonWidth;
Expand Down
1 change: 1 addition & 0 deletions packages/dropdown_button2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Add `barrierBlocksInteraction` to allow interaction with underlying widgets while the dropdown menu is open.
- Properly dispose internal FocusNode when replaced by an external FocusNode.
- Add `mouseCursor` to the dropdown button and MenuItemStyleData to customize the mouse cursor when hovering, closes #416.
- Improve docs and validate selectedItemBuilder length.

## 3.0.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CustomDropdownButton2 extends StatelessWidget {
final ValueListenable<String?>? valueListenable;
final List<String> dropdownItems;
final ValueChanged<String?>? onChanged;
final DropdownButtonBuilder? selectedItemBuilder;
final DropdownButton2Builder? selectedItemBuilder;
final Alignment? hintAlignment;
final Alignment? valueAlignment;
final double? buttonHeight, buttonWidth;
Expand Down
44 changes: 22 additions & 22 deletions packages/dropdown_button2/lib/src/dropdown_button2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const EdgeInsets _kUnalignedButtonPadding = EdgeInsets.zero;
/// A builder to customize dropdown buttons.
///
/// Used by [DropdownButton2.selectedItemBuilder].
///
/// The list of widgets returned by this builder must be exactly the same length
/// as the [DropdownButton2.items] list.
typedef DropdownButton2Builder = Iterable<Widget> Function(BuildContext context);

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

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

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

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

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

effectiveDecoration = effectiveDecoration.copyWith(
Expand Down
6 changes: 3 additions & 3 deletions packages/dropdown_button2/lib/src/dropdown_menu_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class DropdownItem<T> extends _DropdownMenuItemContainer {

/// The value to return if the user selects this menu item.
///
/// Eventually returned in a call to [DropdownButton.onChanged].
/// Eventually returned in a call to [DropdownButton2.onChanged].
final T? value;

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

// The container widget for a menu item created by a [DropdownButton2]. It
// provides the default configuration for [DropdownMenuItem]s, as well as a
// [DropdownButton]'s hint and disabledHint widgets.
// provides the default configuration for [DropdownItem]s, as well as a
// [DropdownButton2]'s hint and disabledHint widgets.
class _DropdownMenuItemContainer extends StatelessWidget {
/// Creates an item for a dropdown menu.
///
Expand Down
9 changes: 4 additions & 5 deletions packages/dropdown_button2/lib/src/dropdown_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -371,11 +371,10 @@ class _DropdownRoutePageState<T> extends State<_DropdownRoutePage<T>> {
void initState() {
super.initState();
// Computing the initialScrollOffset now, before the items have been laid
// out. This only works if the item heights are effectively fixed, i.e. either
// DropdownButton.itemHeight is specified or DropdownButton.itemHeight is null
// and all of the items' intrinsic heights are less than _kMenuItemHeight.
// Otherwise the initialScrollOffset is just a rough approximation based on
// treating the items as if their heights were all equal to _kMenuItemHeight.
// out. This is accurate only if no item sets [DropdownItem.intrinsicHeight]
// to true. When an item uses an intrinsic height, the offset is just a
// rough approximation that uses the declared [DropdownItem.height] as a
// fallback, since the actual height can't be known until layout.
final _MenuLimits menuLimits = widget.route.getMenuLimits(
widget.buttonRect,
widget.constraints.maxHeight,
Expand Down
2 changes: 1 addition & 1 deletion packages/dropdown_button2/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ void _uniqueValueAssert<T>(
}

String assertMessage(T value) {
return "There should be exactly one item with [DropdownButton]'s value: "
return "There should be exactly one item with [DropdownButton2]'s value: "
'$value. \n'
'Either zero or 2 or more [DropdownItem]s were detected '
'with the same value';
Expand Down
2 changes: 1 addition & 1 deletion packages/dropdown_button2/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: dropdown_button2
description: Flutter's core Dropdown Button widget with steady dropdown menu and many options you can customize to your needs.
description: Flutter's core Dropdown Button widget with a steady dropdown menu and many other options you can customize to your needs.
version: 3.0.0
repository: https://github.com/AhmedLSayed9/dropdown_button2
issue_tracker: https://github.com/AhmedLSayed9/dropdown_button2/issues
Expand Down
41 changes: 41 additions & 0 deletions packages/dropdown_button2/test/dropdown_button2_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1053,4 +1053,45 @@ void main() {
},
);
});

group(
'Selected Item Builder',
() {
final menuItems = List<int>.generate(4, (int index) => index);

List<DropdownItem<int>> buildItems() {
return menuItems.map<DropdownItem<int>>((int item) {
return DropdownItem<int>(
value: item,
child: Text(item.toString()),
);
}).toList();
}

testWidgets(
'selectedItemBuilder should assert when it returns a different number of widgets than items',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DropdownButton2<int>(
items: buildItems(),
selectedItemBuilder: (BuildContext context) => const [Text('only one')],
onChanged: (_) {},
),
),
),
);

final Object? exception = tester.takeException();
expect(exception, isA<AssertionError>());
expect(
(exception! as AssertionError).message,
'The selectedItemBuilder must return a list of widgets with the same length as the items list.\n'
'Currently, selectedItemBuilder returns a list of length 1, but items has length 4.',
);
},
);
},
);
}
Loading