Salta al contenuto principale

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),
),
],
);
}
}