Let’s make the number of tasks more manageable via the _limit
server query param, which in this case will return a maximum of 5
resources.
class TasksScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.tasks.watchAll(params: {'_limit': 5});
// ...
}
Hot restarting the app we should only see five tasks, but…
It’s exactly the same as before. Why isn’t this working? 🤔
Turns out watchAll
is wired to show all tasks in local storage. If the server responds with some tasks, this won’t affect older tasks stored locally.
To fix this, watchAll
takes a syncLocal
argument which forces local storage to mirror exactly the resources returned from the remote source. This can be useful to reflect server-side resource deletions, too.
Let’s try this out:
class TasksScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.tasks.watchAll(params: {'_limit': 5}, syncLocal: true);
// ...
}
And it works like a charm:
With a real-world API we would still see all tasks marked as done. We went back to default as our dummy JSON backend does not store data.
Another useful trick is to use clear: true
on local storage configuration:
void main() {
runApp(
ProviderScope(
child: TasksApp(),
overrides: [configureRepositoryLocalStorage(clear: true)],
),
);
}
For more on initialization see here.
Instead of manually reloading/restarting we will now integrate RefreshIndicator
. In the event handler we simply use findAll
and pass the same arguments:
class TasksScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final _newTaskController = useTextEditingController();
final state = ref.tasks.watchAll(params: {'_limit': 5}, syncLocal: true);
if (state.isLoading) {
return CircularProgressIndicator();
}
return RefreshIndicator(
onRefresh: () =>
ref.tasks.findAll(params: {'_limit': 5}, syncLocal: true),
child: ListView(
children: [
TextField(
controller: _newTaskController,
onSubmitted: (value) async {
Task(title: value, completed: false).save();
_newTaskController.clear();
},
),
for (final task in state.model)
ListTile(
leading: Checkbox(
value: task.completed,
onChanged: (value) => task.toggleCompleted().save(),
),
title: Text('${task.title} [id: ${task.id}]'),
),
],
),
);
}
}
Now simply pull to refresh!
A DRY’er alternative would be getting hold of the notifier and calling reload()
on it:
class TasksScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final _newTaskController = useTextEditingController();
final provider =
ref.tasks.watchAllProvider(params: {'_limit': 5}, syncLocal: true);
final state = ref.watch(provider);
if (state.isLoading) {
return CircularProgressIndicator();
}
return RefreshIndicator(
onRefresh: () => ref.read(provider.notifier).reload(),
child: ListView(
// ...
NEXT: Deleting tasks