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