Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions examples/api/lib/material/autocomplete/autocomplete.2.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

/// Flutter code sample for [Autocomplete] that shows how to fetch the options
/// from a remote API.

const Duration fakeAPIDuration = Duration(seconds: 1);

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

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

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Autocomplete - async'),
),
body: const Center(
child: _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? _searchingWithQuery;

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

@override
Widget build(BuildContext context) {
return Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) async {
_searchingWithQuery = textEditingValue.text;
final Iterable<String> options = await _FakeAPI.search(_searchingWithQuery!);

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

_lastOptions = options;
return options;
},
onSelected: (String selection) {
debugPrint('You just selected $selection');
},
);
}
}

// 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());
});
}
}
167 changes: 167 additions & 0 deletions examples/api/lib/material/autocomplete/autocomplete.3.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter/material.dart';

/// Flutter code sample for [Autocomplete] that demonstrates fetching the
/// options asynchronously and debouncing the network calls.

const Duration fakeAPIDuration = Duration(seconds: 1);
const Duration debounceDuration = Duration(milliseconds: 500);

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

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

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Autocomplete - async and debouncing'),
),
body: const Center(
child: _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<String> _lastOptions = <String>[];

late final _Debounceable<Iterable<String>?, String> _debouncedSearch;

// Calls the "remote" API to search with the given query. Returns null when
// the call has been made obsolete.
Future<Iterable<String>?> _search(String query) async {
_currentQuery = query;

// In a real application, there should be some error handling here.
final Iterable<String> options = await _FakeAPI.search(_currentQuery!);

// If another search happened after this one, throw away these options.
if (_currentQuery != query) {
_currentQuery = null;
return null;
}
_currentQuery = null;

return options;
}

@override
void initState() {
super.initState();
_debouncedSearch = _debounce<Iterable<String>?, String>(_search);
}

@override
Widget build(BuildContext context) {
return Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) async {
final Iterable<String>? 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<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());
});
}
}

typedef _Debounceable<S, T> = Future<S?> 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<S, T> _debounce<S, T>(_Debounceable<S?, T> 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<void> _completer = Completer<void>();

void _onComplete() {
_completer.complete();
}

Future<void> 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();
}
Loading