flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
165.02k stars 27.19k forks source link

The initial value of the Autocomplete class is not selected #155159

Open fdemiguel opened 1 week ago

fdemiguel commented 1 week ago

Steps to reproduce

  1. Go to https://api.flutter.dev/flutter/material/Autocomplete-class.html
  2. Write initialValue: TextEditingValue( text: 'bobcat', selection: TextSelection(baseOffset: 0, extentOffset: 2)) as Autocomplete parameter in the first example

Expected results

The 'bobcat' text to be selected

Actual results

The text 'bobcat' is not selected

darshankawar commented 1 week ago

@fdemiguel What flutter version and platform are you seeing this ? I checked with latest master and running below code with initialValue which properly sets the given value:

Screenshot 2024-09-13 at 6 41 52 PM
code ``` import 'dart:async'; import 'package:flutter/material.dart'; /// Flutter code sample for [Autocomplete] that demonstrates fetching the /// options asynchronously and debouncing the network calls, including handling /// network errors. void main() => runApp(const AutocompleteExampleApp()); const Duration fakeAPIDuration = Duration(seconds: 1); const Duration debounceDuration = Duration(milliseconds: 500); class AutocompleteExampleApp extends StatelessWidget { const AutocompleteExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text( 'Autocomplete - async, debouncing, and network errors'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Type below to autocomplete the following possible results: ${_FakeAPI._kOptions}.'), const SizedBox(height: 32.0), const _AsyncAutocomplete(), ], ), ), ), ); } } class _AsyncAutocomplete extends StatefulWidget { const _AsyncAutocomplete(); @override State<_AsyncAutocomplete> createState() => _AsyncAutocompleteState(); } class _AsyncAutocompleteState extends State<_AsyncAutocomplete> { // The query currently being searched for. If null, there is no pending // request. String? _currentQuery; // The most recent options received from the API. late Iterable _lastOptions = []; late final _Debounceable?, String> _debouncedSearch; // Whether to consider the fake network to be offline. bool _networkEnabled = true; // A network error was received on the most recent query. bool _networkError = false; // Calls the "remote" API to search with the given query. Returns null when // the call has been made obsolete. Future?> _search(String query) async { _currentQuery = query; late final Iterable options; try { options = await _FakeAPI.search(_currentQuery!, _networkEnabled); } catch (error) { if (error is _NetworkException) { setState(() { _networkError = true; }); return []; } rethrow; } // If another search happened after this one, throw away these options. if (_currentQuery != query) { return null; } _currentQuery = null; return options; } @override void initState() { super.initState(); _debouncedSearch = _debounce?, String>(_search); } @override Widget build(BuildContext context) { return Container( color: Colors.red, height: 100, child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( _networkEnabled ? 'Network is on, toggle to induce network errors.' : 'Network is off, toggle to allow requests to go through.', ), Switch( value: _networkEnabled, onChanged: (bool? value) { setState(() { _networkEnabled = !_networkEnabled; }); }, ), const SizedBox( height: 32.0, ), Container( color: Colors.amber, padding: EdgeInsets.all(5), child: Autocomplete( initialValue: TextEditingValue(text: 'bobcat'), fieldViewBuilder: (BuildContext context, TextEditingController controller, FocusNode focusNode, VoidCallback onFieldSubmitted) { return TextFormField( decoration: InputDecoration( errorText: _networkError ? 'Network error, please try again.' : null, ), controller: controller, focusNode: focusNode, onFieldSubmitted: (String value) { onFieldSubmitted(); }, ); }, optionsBuilder: (TextEditingValue textEditingValue) async { setState(() { _networkError = false; }); final Iterable? options = await _debouncedSearch(textEditingValue.text); if (options == null) { return _lastOptions; } _lastOptions = options; return options; }, onSelected: (String selection) { debugPrint('You just selected $selection'); }, )), ], ))); } } // Mimics a remote API. class _FakeAPI { static const List _kOptions = [ 'aardvark', 'bobcat', 'chameleon', ]; // Searches the options, but injects a fake "network" delay. static Future> search( String query, bool networkEnabled) async { await Future.delayed(fakeAPIDuration); // Fake 1 second delay. if (!networkEnabled) { throw const _NetworkException(); } if (query == '') { return const Iterable.empty(); } return _kOptions.where((String option) { return option.contains(query.toLowerCase()); }); } } typedef _Debounceable = Future Function(T parameter); /// Returns a new function that is a debounced version of the given function. /// /// This means that the original function will be called only after no calls /// have been made for the given Duration. _Debounceable _debounce(_Debounceable function) { _DebounceTimer? debounceTimer; return (T parameter) async { if (debounceTimer != null && !debounceTimer!.isCompleted) { debounceTimer!.cancel(); } debounceTimer = _DebounceTimer(); try { await debounceTimer!.future; } catch (error) { if (error is _CancelException) { return null; } rethrow; } return function(parameter); }; } // A wrapper around Timer used for debouncing. class _DebounceTimer { _DebounceTimer() { _timer = Timer(debounceDuration, _onComplete); } late final Timer _timer; final Completer _completer = Completer(); void _onComplete() { _completer.complete(); } Future get future => _completer.future; bool get isCompleted => _completer.isCompleted; void cancel() { _timer.cancel(); _completer.completeError(const _CancelException()); } } // An exception indicating that the timer was canceled. class _CancelException implements Exception { const _CancelException(); } // An exception indicating that a network request has failed. class _NetworkException implements Exception { const _NetworkException(); } ```
fdemiguel commented 5 days ago

The problem is not related to the fact that it does not assign the initial value, it is that it does not select the text. In your example, if you write initialValue: TextEditingValue(text: 'bobcat', selection: TextSelection(baseOffset: 0, extentOffset: 6))), you will see that the text bobcat is not selected.

darshankawar commented 4 days ago

I see. Thanks for the update. Seeing the same behavior as reported. This also occurs using RawAutocomplete widget as well.

Stable : 3.24.3
Master: 3.26.0-1.0.pre.129