Search as we type
The content of this page may be outdated.
It will be updated in the future, but for now you may want to refer to the content
in the top of the sidebar instead (introduction/essentials/case-studies/...)
A real world example could be to use FutureProvider
to implement a searchbar.
Usage example: "Search as we type" searchbar
Implementing a "search as we type" can seem daunting at first when using
conventional means.
There are lots of moving parts, such as:
- handling errors.
- debouncing the user input to avoid making network requests on every keystroke.
- cancelling previously pending network requests when the search field changes.
But the combination of FutureProvider
and the power of [ref.watch] can
significantly simplify this task.
A common pattern wanting to perform an asynchronous requests is to split it into multiple providers:
- a [StateNotifierProvider] or
StateProvider
for the parameters of your request (or alternatively use [family]) - a
FutureProvider
, which will do the request by reading the parameters from the other providers/[family].
The first step would be to store the user input somewhere. For this example,
we will use StateProvider
(as the search state is only a single String
):
final searchInputProvider = StateProvider<String>((ref) => '');
We can then connect this provider to a [TextField] by doing:
Consumer(
builder: (context, ref, child) {
return TextField(
onChanged: (value) => ref.read(searchInputProvider.notifier).state = value,
);
},
)
Then, we can create our FutureProvider
which will take care of the request:
final searchProvider = FutureProvider<
<!--
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. 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';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
final searchFieldProvider = StateProvider<String>((ref) => '');
final questionsProvider = FutureProvider<List>((ref) async {
final client = http.Client();
ref.onDispose(client.close);
final search = ref.watch(searchFieldProvider);
Uri uri;
if (search.isEmpty) {
uri = Uri.parse(
'https://api.stackexchange.com/2.3/questions?order=desc&sort=activity&site=stackoverflow',
);
} else {
final encodedQuery = Uri.encodeComponent(search);
uri = Uri.parse(
'https://api.stackexchange.com/2.3/search?order=desc&sort=activity&intitle=$encodedQuery&site=stackoverflow',
);
}
final response = await client.get(uri);
final questions = jsonDecode(response.body);
return questions['items'].map((question) => question['title']).toList();
});
void main() => runApp(const ProviderScope(child: MyApp()));
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return const MaterialApp(home: MyHomePage());
}
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final questions = ref.watch(questionsProvider);
return Scaffold(
appBar: AppBar(title: const Text('Questions')),
body: Column(
children: [
TextField(
onChanged: (value) =>
ref.read(searchFieldProvider.notifier).state = value,
),
Expanded(
child: switch (questions) {
AsyncData(:final value) => ListView.builder(
itemCount: value.length,
itemBuilder: (context, index) {
final question = value[index];
return ListTile(
title: Text(
question.toString(),
),
);
},
);,
AsyncError(:final error) => Center(child: Text('Error $error')),
_ => const Center(child: CircularProgressIndicator()),
},
),
],
),
);
}
}