Resize SearchAnchor suggestions list to content

164 views Asked by At

I'm trying to resize the view of a SearchAnchor to its content. Let's say I have some basic code (taken from here): https://api.flutter.dev/flutter/material/SearchAnchor-class.html. I don't know how many suggestions I'll have to display before hand (I first need to make an API call).

import 'package:flutter/material.dart';

/// Flutter code sample for [SearchAnchor].

const Duration fakeAPIDuration = Duration(seconds: 1);

void main() => runApp(const SearchAnchorAsyncExampleApp());

class SearchAnchorAsyncExampleApp extends StatelessWidget {
  const SearchAnchorAsyncExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('SearchAnchor - async'),
        ),
        body: const Center(
          child: _AsyncSearchAnchor(),
        ),
      ),
    );
  }
}

class _AsyncSearchAnchor extends StatefulWidget {
  const _AsyncSearchAnchor();

  @override
  State<_AsyncSearchAnchor> createState() => _AsyncSearchAnchorState();
}

class _AsyncSearchAnchorState extends State<_AsyncSearchAnchor> {
  // The query currently being searched for. If null, there is no pending
  // request.
  String? _searchingWithQuery;

  // The most recent options received from the API.
  late Iterable<Widget> _lastOptions = <Widget>[];

  @override
  Widget build(BuildContext context) {
    return SearchAnchor(
        builder: (BuildContext context, SearchController controller) {
      return IconButton(
        icon: const Icon(Icons.search),
        onPressed: () {
          controller.openView();
        },
      );
    }, suggestionsBuilder:
            (BuildContext context, SearchController controller) async {
      _searchingWithQuery = controller.text;
      final List<String> options =
          (await _FakeAPI.search(_searchingWithQuery!)).toList();

      // If another search happened after this one, throw away these options.
      // Use the previous options instead and wait for the newer request to
      // finish.
      if (_searchingWithQuery != controller.text) {
        return _lastOptions;
      }

      _lastOptions = List<ListTile>.generate(options.length, (int index) {
        final String item = options[index];
        return ListTile(
          title: Text(item),
        );
      });

      return _lastOptions;
    });
  }
}

// Mimics a remote API.
class _FakeAPI {
  static const List<String> _kOptions = <String>[
    'aardvark',
    'bobcat',
    'chameleon',
  ];

  // Searches the options, but injects a fake "network" delay.
  static Future<Iterable<String>> search(String query) async {
    await Future<void>.delayed(fakeAPIDuration); // Fake 1 second delay.
    if (query == '') {
      return const Iterable<String>.empty();
    }
    return _kOptions.where((String option) {
      return option.contains(query.toLowerCase());
    });
  }
}

The code above runs, but the suggestions list is way too big. Even after filling up the list of suggestions, it's showing too much empty space. I would like to display a shorter (and empty) suggestion list at the beginning, and after the first API call, I would like the list to resize to its content.

Do you have any idea how to do that?

enter image description here enter image description here

1

There are 1 answers

0
Simon On

Not very dynamic, but one can cap the height. It seems that the view height is clambed by the SearchAnchor's viewConstraints.

// Flutter's search_anchor.dart
final BoxConstraints effectiveConstraints = viewConstraints ?? viewTheme.constraints ?? viewDefaults.constraints!;
// ....
final double viewHeight = clampDouble(screenSize.height * 2 / 3, effectiveConstraints.minHeight, effectiveConstraints.maxHeight);

Example usage

SearchAnchor(
      isFullScreen: false,
      viewConstraints: BoxConstraints(
        minHeight: kToolbarHeight,
        maxHeight: kToolbarHeight * 4,
     ),
     // ...
)

Probably not the cleanest way to achive dynamic height but you could also use a multiple of the length of _lastOptions for the maxHeight. E.g. maxHeight: kToolbarHeight * _lastOptions.length,