Skip to content

Add async filter function #162

@julien-levarlet

Description

@julien-levarlet

Use case

I use the SearchableList.async widget in my app to fetch data from a database and display it to the user. But with a large database, it feels that it is not very convenient.

With the current version of the async constructor, I have to get all my objects from my database in the asyncListCallback (so all of them are copied in memory). And then when filtering, I have to iterate over all element to check if it contains the string. This is not very efficient for many elements.

Proposal

What I would like to do is to call a filtering function I wrote (which is async) that fetch a subset of elements from my database that contains the string. This function is easier to optimize, and limit jank on older phones.

As an example I wrote this code that create a search bar with an async filter function.

class SearchList extends StatefulWidget {
  const SearchList({
    super.key,
  });

  @override
  State<SearchList> createState() => _SearchListState();
}

class _SearchListState extends State<SearchList> {
  // This will hold the results from our database search
  List<MyObjects> _filtered = [];

  // The debouncer will wait 300ms after the user stops typing
  final _debouncer = Debouncer(milliseconds: 300);
  final TextEditingController _textController = TextEditingController();
  bool _isLoading = false;

  final Key _listViewKey = UniqueKey();

  @override
  void initState() {
    super.initState();
    // Fetch an initial list
    _fetch("");
  }

  /// Fetches the database based on the search query.
  Future<void> _fetch(String query) async {
    if (mounted) setState(() => _isLoading = true);

    // Call the DAO with the actual query. We add a limit to keep it snappy.
    final results = await MyDatabase.instance.get(query, limit: 100);

    if (mounted) {
      setState(() {
        _filtered = results;
        _isLoading = false;
      });
    }
  }

  @override
  void dispose() {
    _debouncer.dispose();
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: _textController,
          onChanged: (query) {
            _debouncer.run(() => _fetch(query));
          },
          decoration: InputDecoration(
            suffixIcon: _isLoading
                ? const Padding(
                    padding: EdgeInsets.all(10.0),
                    child: SizedBox(
                        height: 10,
                        width: 10,
                        child: CircularProgressIndicator(strokeWidth: 2.0)),
                  )
                : const Icon(Icons.search),
            hintText: "Search",
            border: OutlineInputBorder(
              borderSide: BorderSide(
                width: 8.0,
                color: Theme.of(context).colorScheme.primary,
              ),
              borderRadius: BorderRadius.circular(5.0),
            ),
          ),
        ),
        const SizedBox(height: 10),
        Expanded(
            child: _filtered.isEmpty && !_isLoading
                ? Center(
                    child: Text(
                      "Not found",
                      textAlign: TextAlign.center,
                    ),
                  )
                : ListView.separated(
                    key: _listViewKey,
                    padding: const EdgeInsets.symmetric(vertical: 10),
                    itemCount: _filtered.length,
                    itemBuilder: (context, index) {
                      return MyTile(myObject: _filtered[index]);
                    },
                    separatorBuilder: (context, index) {
                      return const SizedBox(height: 5);
                    },
                  )),
      ],
    );
  }
}

Do you think this use case is relevant for your package ?
If yes, I will try to integrate it in the async constructor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions