Flutter Infinite Dropdown
A fully customizable Flutter dropdown widget with built-in infinite scroll pagination β no boilerplate, just drop it in. π
Preview
 |
 |
| Android |
iOS |
Features
- Fully customizable dropdown list items via
itemBuilder
- Infinite scroll pagination β fires
onBottomRefresh when the user reaches the bottom
- Custom loading indicator (or use the default
CircularProgressIndicator)
- Optional item dividers with
isSeparator
- Controller API to stop the loader after your API call completes
- Zero external dependencies β only Flutter
Installation
flutter pub add flutter_infinite_dropdown
import 'package:flutter_infinite_dropdown/flutter_infinite_dropdown.dart';
Requires Flutter β₯ 3.24.0 Β· Dart β₯ 3.5.0
Quick Start
final controller = InfiniteDropdownController();
List<String> items = ['Apple', 'Banana', 'Cherry'];
InfiniteDropdown<String>(
controller: controller,
sourceWidget: const Text('Tap to open'),
dropdownWidget: const SizedBox.shrink(),
data: items,
itemBuilder: (context, index, item) => ListTile(title: Text(item)),
onBottomRefresh: () async {
await Future.delayed(const Duration(seconds: 2));
items.addAll(['Date', 'Elderberry', 'Fig']);
controller.stopLoading(); // hide the loader
},
)
InfiniteDropdown
Parameters
| Parameter |
Type |
Default |
Description |
sourceWidget |
Widget |
required |
The widget that toggles the dropdown on tap |
dropdownWidget |
Widget |
required |
Reserved for custom dropdown header |
data |
List<T> |
required |
Items to display in the list |
itemBuilder |
Widget Function(BuildContext, int, T) |
required |
Builder for each list row |
controller |
InfiniteDropdownController? |
null |
Controller to call stopLoading() after pagination completes |
onBottomRefresh |
Future<void> Function()? |
null |
Called when the user scrolls to the bottom of the list |
dropdownHeight |
double? |
120 |
Height of the dropdown overlay |
spacing |
double? |
8.0 |
Gap between the source widget and the dropdown |
isSeparator |
bool |
false |
Show a Divider between list items |
progressIndicatorColor |
Color? |
null |
Color of the default loading indicator |
customIndicator |
Widget? |
null |
Replace the default loader with a custom widget |
InfiniteDropdownController
| Method |
Description |
stopLoading() |
Hides the loading indicator at the bottom of the list |
Examples
Basic dropdown
InfiniteDropdown<String>(
sourceWidget: Container(
width: 200,
height: 48,
color: Colors.blue,
child: const Center(
child: Text('Select an item', style: TextStyle(color: Colors.white)),
),
),
dropdownWidget: const SizedBox.shrink(),
data: const ['Option A', 'Option B', 'Option C'],
itemBuilder: (context, index, item) => ListTile(
title: Text(item),
onTap: () => debugPrint('Selected: $item'),
),
)
class _MyState extends State<MyPage> {
final controller = InfiniteDropdownController();
List<String> items = List.generate(10, (i) => 'Item $i');
@override
Widget build(BuildContext context) {
return InfiniteDropdown<String>(
controller: controller,
sourceWidget: const Text('Open'),
dropdownWidget: const SizedBox.shrink(),
data: items,
dropdownHeight: 220,
isSeparator: true,
itemBuilder: (context, index, item) => ListTile(title: Text(item)),
onBottomRefresh: () async {
// Simulate network call
await Future.delayed(const Duration(seconds: 2));
setState(() {
items.addAll(List.generate(10, (i) => 'Item ${items.length + i}'));
});
controller.stopLoading();
},
);
}
}
Custom loading indicator
InfiniteDropdown<String>(
sourceWidget: const Text('Open'),
dropdownWidget: const SizedBox.shrink(),
data: items,
itemBuilder: (context, index, item) => ListTile(title: Text(item)),
customIndicator: const Padding(
padding: EdgeInsets.all(12),
child: Center(child: Text('Loading moreβ¦')),
),
)
| Android |
iOS |
Web |
macOS |
Linux |
Windows |
| β
|
β
|
β
|
β
|
β
|
β
|
If this package saved you time, a coffee would be appreciated β
MIT Β© Vishal Zaveri