1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:flutter/material.dart';
8
9/// Flutter code sample for [Autocomplete] that demonstrates fetching the
10/// options asynchronously and debouncing the network calls.
11
12const Duration fakeAPIDuration = Duration(seconds: 1);
13const Duration debounceDuration = Duration(milliseconds: 500);
14
15void main() => runApp(const AutocompleteExampleApp());
16
17class AutocompleteExampleApp extends StatelessWidget {
18 const AutocompleteExampleApp({super.key});
19
20 @override
21 Widget build(BuildContext context) {
22 return MaterialApp(
23 home: Scaffold(
24 appBar: AppBar(title: const Text('Autocomplete - async and debouncing')),
25 body: Center(
26 child: Column(
27 mainAxisAlignment: MainAxisAlignment.center,
28 children: <Widget>[
29 Text(
30 'Type below to autocomplete the following possible results: ${_FakeAPI._kOptions}.',
31 ),
32 const _AsyncAutocomplete(),
33 ],
34 ),
35 ),
36 ),
37 );
38 }
39}
40
41class _AsyncAutocomplete extends StatefulWidget {
42 const _AsyncAutocomplete();
43
44 @override
45 State<_AsyncAutocomplete> createState() => _AsyncAutocompleteState();
46}
47
48class _AsyncAutocompleteState extends State<_AsyncAutocomplete> {
49 // The query currently being searched for. If null, there is no pending
50 // request.
51 String? _currentQuery;
52
53 // The most recent options received from the API.
54 late Iterable<String> _lastOptions = <String>[];
55
56 late final _Debounceable<Iterable<String>?, String> _debouncedSearch;
57
58 // Calls the "remote" API to search with the given query. Returns null when
59 // the call has been made obsolete.
60 Future<Iterable<String>?> _search(String query) async {
61 _currentQuery = query;
62
63 // In a real application, there should be some error handling here.
64 final Iterable<String> options = await _FakeAPI.search(_currentQuery!);
65
66 // If another search happened after this one, throw away these options.
67 if (_currentQuery != query) {
68 return null;
69 }
70 _currentQuery = null;
71
72 return options;
73 }
74
75 @override
76 void initState() {
77 super.initState();
78 _debouncedSearch = _debounce<Iterable<String>?, String>(_search);
79 }
80
81 @override
82 Widget build(BuildContext context) {
83 return Autocomplete<String>(
84 optionsBuilder: (TextEditingValue textEditingValue) async {
85 final Iterable<String>? options = await _debouncedSearch(textEditingValue.text);
86 if (options == null) {
87 return _lastOptions;
88 }
89 _lastOptions = options;
90 return options;
91 },
92 onSelected: (String selection) {
93 debugPrint('You just selected $selection');
94 },
95 );
96 }
97}
98
99// Mimics a remote API.
100class _FakeAPI {
101 static const List<String> _kOptions = <String>['aardvark', 'bobcat', 'chameleon'];
102
103 // Searches the options, but injects a fake "network" delay.
104 static Future<Iterable<String>> search(String query) async {
105 await Future<void>.delayed(fakeAPIDuration); // Fake 1 second delay.
106 if (query == '') {
107 return const Iterable<String>.empty();
108 }
109 return _kOptions.where((String option) {
110 return option.contains(query.toLowerCase());
111 });
112 }
113}
114
115typedef _Debounceable<S, T> = Future<S?> Function(T parameter);
116
117/// Returns a new function that is a debounced version of the given function.
118///
119/// This means that the original function will be called only after no calls
120/// have been made for the given Duration.
121_Debounceable<S, T> _debounce<S, T>(_Debounceable<S?, T> function) {
122 _DebounceTimer? debounceTimer;
123
124 return (T parameter) async {
125 if (debounceTimer != null && !debounceTimer!.isCompleted) {
126 debounceTimer!.cancel();
127 }
128 debounceTimer = _DebounceTimer();
129 try {
130 await debounceTimer!.future;
131 } on _CancelException {
132 return null;
133 }
134 return function(parameter);
135 };
136}
137
138// A wrapper around Timer used for debouncing.
139class _DebounceTimer {
140 _DebounceTimer() {
141 _timer = Timer(debounceDuration, _onComplete);
142 }
143
144 late final Timer _timer;
145 final Completer<void> _completer = Completer<void>();
146
147 void _onComplete() {
148 _completer.complete();
149 }
150
151 Future<void> get future => _completer.future;
152
153 bool get isCompleted => _completer.isCompleted;
154
155 void cancel() {
156 _timer.cancel();
157 _completer.completeError(const _CancelException());
158 }
159}
160
161// An exception indicating that the timer was canceled.
162class _CancelException implements Exception {
163 const _CancelException();
164}
165

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com