Searchable ListView


An easy way to filter lists

Features

  • Filter list view easily
  • Filter async list
  • Filter expansion list
  • Sort list items
  • Support async callback in rendering list
  • Support pagination
  • Pull to refresh list
  • Sliver scroll animation effect
  • Customizable sort widget
  • Customizable loading when async callback is loading
  • Customizable error widget
  • Display custom widget when list is empty
  • Customize search text field
  • Change keyboard input type and keyboard submit button
  • Add focus on search text field
  • Add on item pressed callback
  • Customize search text style
  • Clear icon button in search to easily clear text
  • Customizable scroll direction
  • Searchable list with seperator builder
  • Customizable text field position
  • Customizable text style in search field
  • Customize autocomplete options
  • Customizable secondary widget alongside search
  • Close automatically keyboard when scrolling on listview

Getting Started

In order to add searchable listview package to your project add this line to your pubspec.yaml file

dependencies:
	searchable_listview: ^2.11.2

Attributes

`


  /// Initial list of all elements that will be displayed.
  ///to filter the [initialList] you need provide [filter] callback
  late List<T> initialList;

  /// Callback to filter the list based on the given search value.
  /// Invoked on changing the text field search if ```searchType == SEARCH_TYPE.onEdit```
  /// or invoked when submiting the text field if ```searchType == SEARCH_TYPE.onSubmit```.
  /// You should return a list of filtered elements.
  List<T> Function(String query)? filter;
  
  ///Async callback that return list to be displayed with future builder
  ///to filter the [asyncListCallback] result you need provide [asyncListFilter]
  Future<List<T>?> Function()? asyncListCallback;

  ///Callback invoked when filtring the searchable list
  ///used when providing [asyncListCallback]
  ///can't be null when [asyncListCallback] isn't null
  late List<T> Function(String, List<T>)? asyncListFilter;

  ///Loading widget displayed when [asyncListCallback] is loading
  ///if nothing is provided in [loadingWidget] searchable list will display a [CircularProgressIndicator]
  Widget? loadingWidget;

  ///error widget displayed when [asyncListCallback] result is null
  ///if nothing is provided in [errorWidget] searchable list will display a [Icon]
  Widget? errorWidget;
  
  /// Builder function that generates the ListView items
  /// based on the returned <T> type item
  late Widget Function(T item)? itemBuilder;

  /// Builder function that generates the Expansion listView items
  /// [expansionGroupIndex] : expansion group index
  /// [listItem] the current item model that will be rendered.
  /// Used only for expansion list constructor
  late Widget Function(int expansionGroupIndex, T listItem)?
      expansionListBuilder;

  /// The widget to be displayed when the filter returns an empty list.
  /// Defaults to `const SizedBox.shrink()`.
  final Widget emptyWidget;

  /// Text editing controller applied on the search field.
  /// Defaults to null.
  late TextEditingController? searchTextController;

  /// The keyboard action key
  /// Defaults to [TextInputAction.done].
  final TextInputAction keyboardAction;

  /// The text field input decoration
  /// Defaults to null.
  final InputDecoration? inputDecoration;

  /// The style for the input text field
  /// Defaults to null.
  final TextStyle? style;

  /// The keyboard text input type
  /// Defaults to [TextInputType.text]
  final TextInputType textInputType;

  /// Callback function invoked when submiting the search text field
  final Function(String?)? onSubmitSearch;

  /// The search type on submiting text field or when changing the text field value
  ///```dart
  ///SEARCH_TYPE.onEdit,
  ///SEARCH_TYPE.onSubmit
  ///```
  /// Defaults to [SearchMode.onEdit].
  final SearchMode searchMode;

  /// Indicate whether the text field input should be obscured or not.
  /// Defaults to `false`.
  final bool obscureText;

  /// Indicate if the search text field is enabled or not.
  /// Defaults to `true`.
  final bool searchFieldEnabled;

  /// The focus node applied on the search text field
  final FocusNode? focusNode;

  /// Function invoked when pressing on item
  /// Defaults to null
  final void Function(T)? onItemSelected;

  /// Indicate whether the clear icon will be displayed or not
  /// by default it's true, to display the clear icon the inputDecoration should not contains suffix icon
  /// otherwise the initial suffix icon will be displayed
  final bool displayClearIcon;

  /// The color applied on the suffix icon (if `displayClearIcon = true`).
  /// Defaults to [Colors.grey].
  final Color defaultSuffixIconColor;

  ///An async callback invoked when dragging down the list
  ///if onRefresh is nullable the drag to refresh is not applied
  late Future<void> Function()? onRefresh;

  ///Builder callback required  when using [seperated] constructor
  ///return the Widget that will seperate all the elements inside the list
  late Widget Function(BuildContext context, int index)? seperatorBuilder;

  ///The scroll direction of the list
  ///by default [Axis.vertical]
  Axis scrollDirection = Axis.vertical;

  ///The position of the text field (bottom or top)
  ///by default the textfield is displayed on top
  SearchTextPosition searchTextPosition = SearchTextPosition.top;

  ///Callback function invoked each time the listview
  ///reached the bottom
  ///used to create pagination in listview
  Future<dynamic> Function()? onPaginate;

  ///space between the search textfield and the list
  ///by default the padding is set to 20
  final double spaceBetweenSearchAndList;

  ///cusor color used in the search textfield
  final Color? cursorColor;

  ///max lines attribute used in the search textfield
  final int? maxLines;

  ///max length attribute used in the search field
  final int? maxLength;

  ///the text alignement of the search field
  ///by default the alignement is start
  final TextAlign textAlign;

  ///List of strings  to display in an auto complete field
  ///by default list is empty so a simple text field is displayed
  final List<String> autoCompleteHints;

  ///secondary widget will be displayed alongside the search field
  ///by default it's null
  final Widget? secondaryWidget;

  ///Map of data used to build  searchable expansion list
  ///required when using [expansion] constructor
  late Map<dynamic, List<T>> expansionListData;

  ///callback used when filtering the expansion list
  ///required when using [expansion] constructor
  late Map<dynamic, List<T>> Function(String)? filterExpansionData;

  ///the expansion list title widget builder
  ///required when using [expansion] constructor
  late Widget Function(dynamic) expansionTitleBuilder;

  ///physics attributes used in listview widget
  late ScrollPhysics? physics;

  ///shrinkWrap used in listview widget, not used in sliver searchable list
  ///by default `shrinkWrap = false`
  late bool shrinkWrap;

  ///item extent of the listview
  late double? itemExtent;

  ///listview item padding
  late EdgeInsetsGeometry? listViewPadding;

  ///list items reverse attributes
  ///by default `reverse = false`
  ///not available for sliver listview constructor
  late bool reverse;

  ///Predicate callback invoked when sorting list items
  ///required when `displaySortWidget` is True
  late int Function(T a, T b)? sortPredicate;

  ///Widget displayed when sorting list
  /// available only if `displaySortWidget` is True
  late Widget? sortWidget;

  ///Scroll controller passed to listview widget
  ///by default listview uses scrollcontroller with a listener for pagination if `onPaginate = true`
  ///or `closeKeyboardWhenScrolling = true` to close keyboard when scrolling
  ScrollController? scrollController;

  ///indicates whether the keyboard will be closed when scrolling or not
  ///by default `closeKeyboardWhenScrolling = true`
  final bool closeKeyboardWhenScrolling;

  ///indicate whether the expansion will be shown or not when the expansion group is empty
  late bool hideEmptyExpansionItems = false;

  ///Indicate whether the expansion tile will be enabled or not
  late bool expansionTileEnabled = true;

`

Migration:

  • If you are using the 1.5.3 version or older and you are using sliverScrollEffect parameter, for 1.6.0 version and above this parameter is replaced with a constructor SearchableList.sliver

  • If you are migrating to 2.5.0 version you need to change the builder callback as now it provide the element index and not the element object

  • If you are migrating to 2.8.0 version the seperated constructor is no longer available instead use seperatorBuilder in both default constructor and async constructor

Implementation

Default constructor


SearchableList<Object>

Used to create simple listview with search field (with other attributes to customize your own listview)

Async constructor


SearchableList<Object>.async

Used to render listview from a future callback (it require an async callback that return List of objects)

Expansion constructor


SearchableList<Object>.expansion

Used to create expansion listview with search field (with other attributes to customize your own expansion list)

Sliver effect constructor


SearchableList.sliver

Used to create a listview with sliver scrolling effect (with other attributes to customize your own listview)

Simple implementation

SearchableList<Actor>(
  initialList: actors,
  itemBuilder: (Actor user) => UserItem(user: user),
  filter: (value) => actors.where((element) => element.name.toLowerCase().contains(value),).toList(),
  emptyWidget:  const EmptyView(),
  inputDecoration: InputDecoration(
  labelText: "Search Actor",
	fillColor: Colors.white,
	focusedBorder: OutlineInputBorder(
	  borderSide: const BorderSide(
	    color: Colors.blue,
		width: 1.0,
	  ),
	  borderRadius: BorderRadius.circular(10.0),
	),
  ),
),

Expansion list with search implementation

SearchableList<Actor>.expansion(
  expansionListData: mapOfActors,
  expansionTitleBuilder: (p0) {
    return Container(
      color: Colors.grey[300],
      width: MediaQuery.of(context).size.width * 0.8,
      height: 30,
      child: Center(
        child: Text(p0.toString()),
          ),
        );
      },
      filterExpansionData: (p0) {
        final filteredMap = {
          for (final entry in mapOfActors.entries)
            entry.key: (mapOfActors[entry.key] ?? [])
                .where((element) => element.name.contains(p0))
                .toList()
        };
        return filteredMap;
      },
      style: const TextStyle(fontSize: 25),
      expansionListBuilder: (int index, Actor _actor) {
        return ActorItem(
          actor: _actor,
        );
      },
      hideEmptyExpansionItems: true,
      emptyWidget: const EmptyView(),
      inputDecoration: InputDecoration(
        labelText: "Search Actor",
        fillColor: Colors.white,
        focusedBorder: OutlineInputBorder(
          borderSide: const BorderSide(
            color: Colors.blue,
            width: 1.0,
          ),
          borderRadius: BorderRadius.circular(10.0),
        ),
      ),
    );
  }

Async callback build implementation

SearchableList<Actor>.async(
                onPaginate: () async {
                  await Future.delayed(const Duration(milliseconds: 1000));
                  setState(() {
                    actors.addAll([
                      Actor(age: 22, name: 'Fathi', lastName: 'Hadawi'),
                      Actor(age: 22, name: 'Hichem', lastName: 'Rostom'),
                      Actor(age: 22, name: 'Kamel', lastName: 'Twati'),
                    ]);
                  });
                },
                itemBuilder: (Actor actor) => ActorItem(actor: actor),
                loadingWidget: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: const [
                    CircularProgressIndicator(),
                    SizedBox(
                      height: 20,
                    ),
                    Text('Loading actors...')
                  ],
                ),
                errorWidget: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: const [
                    Icon(
                      Icons.error,
                      color: Colors.red,
                    ),
                    SizedBox(
                      height: 20,
                    ),
                    Text('Error while fetching actors')
                  ],
                ),
                asyncListCallback: () async {
                  await Future.delayed(
                    const Duration(
                      milliseconds: 10000,
                    ),
                  );
                  return actors;
                },
                asyncListFilter: (q, list) {
                  return list
                      .where((element) => element.name.contains(q))
                      .toList();
                },
                emptyWidget: const EmptyView(),
                onRefresh: () async {},
                onItemSelected: (Actor item) {},
                inputDecoration: InputDecoration(
                  labelText: "Search Actor",
                  fillColor: Colors.white,
                  focusedBorder: OutlineInputBorder(
                    borderSide: const BorderSide(
                      color: Colors.blue,
                      width: 1.0,
                    ),
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                ),
              )

Sliver scroll example

SearchableList<Actor>.sliver(
  initialList: actors,
  itemBuilder: (Actor user) => UserItem(user: user),
  filter: (value) => actors.where((element) => element.name.toLowerCase().contains(value),).toList(),
  emptyWidget:  const EmptyView(),
  inputDecoration: InputDecoration(
    labelText: "Search Actor",
	fillColor: Colors.white,
	focusedBorder: OutlineInputBorder(
	  borderSide: const BorderSide(
	    color: Colors.blue,
		width: 1.0,
	  ),
	  borderRadius: BorderRadius.circular(10.0),
	),
  ),
),

Searchable list with sort widget

SearchableList<Actor>(
    sortWidget: Icon(Icons.sort),
    sortPredicate: (a, b) => a.age.compareTo(b.age),
    itemBuilder: (item) {
      return ActorItem(actor: item);
    },
    initialList: actors,
    filter: (p0) {
      return actors.where((element) => element.name.contains(p0)).toList();
    },
    inputDecoration: InputDecoration(
      labelText: "Search Actor",
      fillColor: Colors.white,
      focusedBorder: OutlineInputBorder(
        borderSide: const BorderSide(
          color: Colors.blue,
          width: 1.0,
        ),
        borderRadius: BorderRadius.circular(10.0),
      ),
    ),
)

Searchable list with auto closing keyboard when scrolling

SearchableList<Actor>(
    sortWidget: Icon(Icons.sort),
    sortPredicate: (a, b) => a.age.compareTo(b.age),
    itemBuilder: (item) {
      return ActorItem(actor: item);
    },
    initialList: actors,
    filter: (p0) {
      return actors.where((element) => element.name.contains(p0)).toList();
    },
    inputDecoration: InputDecoration(
      labelText: "Search Actor",
      fillColor: Colors.white,
      focusedBorder: OutlineInputBorder(
        borderSide: const BorderSide(
          color: Colors.blue,
          width: 1.0,
        ),
        borderRadius: BorderRadius.circular(10.0),
      ),
    ),
    closeKeyboardWhenScrolling: true
)

Contribution

Of course the project is open source, and you can contribute to it repository link

  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.

Contributors