StateNotifierProvider
StateNotifierProvider
è un provider usato per ascoltare ed esporre uno StateNotifier (dal package state_notifier, che Riverpod ri-esporta).
StateNotifierProvider
unito con StateNotifier è la soluzione consigliata da Riverpod per gestire lo stato in reazione all'interazione dell'utente.
Viene usato in genere per:
- esporre uno stato immutabile che può cambiare nel tempo dopo aver reagito ad eventi personalizzabili.
- centralizzare la logica per modificare lo stato (aka "business logic") in un singolo posto, migliorando la mantenibilità nel tempo.
Come esempio d'uso, potremmo usare StateNotifierProvider
per implementare una todo-list (lista di todo).
Fare ciò ci permette di esporre dei metodi come addTodo
per lasciare che l'UI modifichi
la todo-list in base alle interazioni dell'utente:
// Lo stato del nostro StateNotifier dovrebbe essere immutabile.
// Potremmo usare anche packages come Freezed per aiutarci con l'implementazione.
class Todo {
const Todo({required this.id, required this.description, required this.completed});
// Tutte le proprietà dovrebbero essere `final` nella nostra classe.
final String id;
final String description;
final bool completed;
// Dato che Todo è immutabile, implementiamo un metodo che ci permette di
// clonare l'oggetto Todo con un contenuto leggermente diverso.
Todo copyWith({String? id, String? description, bool? completed}) {
return Todo(
id: id ?? this.id,
description: description ?? this.description,
completed: completed ?? this.completed,
);
}
}
// La classe StateNotifier sarà passata al nostro StateNotifierProvider.
// Questa classe non dovrebbe esporre lo stato al di fuori della sua proprietà "state"
// il che significa nessuna proprietà o getter pubblico!
// I metodi pubblici di questa classe sono quelli che consentiranno alla UI di modificare lo stato.
class TodosNotifier extends StateNotifier<List<Todo>> {
// Inizializzamo la lista dei todo con una lista vuota
TodosNotifier() : super([]);
// Consentiamo alla UI di aggiungere i todo
void addTodo(Todo todo) {
// Poichè il nostro stato è immutabile, non siamo autorizzati a fare `state.add(todo)`.
// Dovremmo invece creare una nuova lista di todo contenente
// gli elementi precedenti e il nuovo
// Usare lo spread operator di Dart qui è d'aiuto!
state = [...state, todo];
// Non c'è bisogno di chiamare "notifiyListeners" o qualcosa di simile.
// Chiamare "state =" ricostruirà automaticamente la UI quando necessario.
}
// Consentiamo di rimuovere i todo
void removeTodo(String todoId) {
// Di nuovo, il nostro stato è immutabile. Quindi facciamo una nuova lista
// invece di modificare la lista esistente.
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}
// Contrassegniamo il todo come completato
void toggle(String todoId) {
state = [
for (final todo in state)
// contrassegniamo solo il todo corrispondente come completato
if (todo.id == todoId)
// Usiamo il metodo `copyWith` implementato prima per aiutarci nel
// modificare lo stato
todo.copyWith(completed: !todo.completed)
else
// gli altri todo non sono modificati
todo,
];
}
}
// Infine, usiamo StateNotifierProvider per consentire all'UI di interagire con
// la classe TodosNotifier
final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
return TodosNotifier();
});
Ora che abbiamo definito uno StateNotifierProvider
,
possiamo usarlo per interagire con la todo-list nella nostra interfaccia grafica:
class TodoListView extends ConsumerWidget {
const TodoListView({super.key});
Widget build(BuildContext context, WidgetRef ref) {
// ricostruisce il widget quando la todo-list cambia
List<Todo> todos = ref.watch(todosProvider);
// Renderizziamo i todo in una list view scrollabile
return ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// When tapping on the todo, change its completed status
onChanged: (value) => ref.read(todosProvider.notifier).toggle(todo.id),
title: Text(todo.description),
),
],
);
}
}