[{"data":1,"prerenderedAt":76},["ShallowReactive",2],{"tutorial-gestire-gli-errori-in-flutter-con-either-e-il-pacchetto-dartz":3,"comments-tutorial-gestire-gli-errori-in-flutter-con-either-e-il-pacchetto-dartz":75},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":10,"video_url":10,"video":10,"difficulty":11,"estimated_minutes":12,"flutter_version":13,"status":14,"published_at":15,"meta_title":16,"meta_description":17,"category":18,"author":22,"steps":25},57,"Gestire gli errori in Flutter con Either e il pacchetto dartz","gestire-gli-errori-in-flutter-con-either-e-il-pacchetto-dartz","Impara a modellare successi ed errori in modo type-safe usando il tipo Either del pacchetto dartz, eliminando le eccezioni non gestite dal tuo codice Flutter.","## Perché gestire gli errori con Either?\n\nIn molti progetti Flutter la gestione degli errori si basa su `try\u002Fcatch` sparsi ovunque, con il rischio di dimenticare qualche eccezione e far crashare l'app. Un approccio più robusto, ispirato alla programmazione funzionale, consiste nel rendere l'errore **esplicito nella firma dei metodi**.\n\nIl tipo `Either\u003CL, R>` del pacchetto [dartz](https:\u002F\u002Fpub.dev\u002Fpackages\u002Fdartz) rappresenta un valore che può essere di due tipi: `Left` (per convenzione l'errore) oppure `Right` (per convenzione il successo). In questo modo il compilatore ti obbliga a gestire entrambi i casi.\n\nIn questo tutorial costruiremo un piccolo repository che recupera un utente e restituisce un `Either\u003CFailure, User>`, mostrando come gestire il risultato nella UI in modo pulito e sicuro.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F585d9e11-9e21-4d13-ae79-66a6fbe52b4b.jpg",null,"intermediate",35,"3.x","published","2026-08-08T04:30:44+00:00","Gestione errori in Flutter con Either e dartz","Guida pratica per gestire successi ed errori in modo type-safe in Flutter usando il tipo Either del pacchetto dartz.",{"id":19,"name":20,"slug":21},3,"Best practice","best-practice",{"id":23,"name":24},1,"Flutter Bot",[26,33,41,47,54,61,68],{"id":27,"position":23,"title":28,"body":29,"code_snippet":30,"code_language":31,"expected_result":32,"demo_url":10,"video_url":10},383,"Aggiungere le dipendenze","Aggiungi il pacchetto `dartz` al tuo progetto. Fornisce il tipo `Either` e molte altre strutture della programmazione funzionale.\n\nEsegui il comando da terminale oppure aggiungi manualmente la dipendenza al file `pubspec.yaml`.","flutter pub add dartz","bash","Nel pubspec.yaml comparirà la dipendenza `dartz` e il progetto scaricherà il pacchetto senza errori.",{"id":34,"position":35,"title":36,"body":37,"code_snippet":38,"code_language":39,"expected_result":40,"demo_url":10,"video_url":10},384,2,"Definire i tipi di errore (Failure)","Invece di lanciare eccezioni generiche, definiamo una gerarchia di errori tipizzati. Questo rende chiaro quali errori può restituire ciascuna funzione e facilita la loro gestione nella UI.\n\nCreiamo una classe base `Failure` con alcune sottoclassi specifiche.","abstract class Failure {\n  final String message;\n  const Failure(this.message);\n}\n\nclass ServerFailure extends Failure {\n  const ServerFailure([super.message = 'Errore del server']);\n}\n\nclass NetworkFailure extends Failure {\n  const NetworkFailure([super.message = 'Nessuna connessione']);\n}\n\nclass NotFoundFailure extends Failure {\n  const NotFoundFailure([super.message = 'Risorsa non trovata']);\n}","dart","Hai una gerarchia di errori riutilizzabile che rappresenta i possibili fallimenti dell'app.",{"id":42,"position":19,"title":43,"body":44,"code_snippet":45,"code_language":39,"expected_result":46,"demo_url":10,"video_url":10},385,"Creare il modello User","Definiamo un semplice modello `User` che rappresenta il risultato di successo. Includiamo un factory `fromJson` per simulare la deserializzazione di una risposta REST.","class User {\n  final int id;\n  final String name;\n  final String email;\n\n  const User({required this.id, required this.name, required this.email});\n\n  factory User.fromJson(Map\u003CString, dynamic> json) {\n    return User(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      email: json['email'] as String,\n    );\n  }\n}","Il modello `User` è pronto per essere usato come valore di successo nel tipo Either.",{"id":48,"position":49,"title":50,"body":51,"code_snippet":52,"code_language":39,"expected_result":53,"demo_url":10,"video_url":10},386,4,"Implementare il repository che restituisce Either","Ora creiamo il repository. Invece di lasciar propagare le eccezioni, le catturiamo internamente e le convertiamo in `Left(Failure)`. Il caso di successo viene incapsulato in `Right(User)`.\n\nNota la firma del metodo: `Future\u003CEither\u003CFailure, User>>` comunica chiaramente a chi lo usa che l'operazione può fallire.","import 'package:dartz\u002Fdartz.dart';\n\nclass UserRepository {\n  Future\u003CEither\u003CFailure, User>> getUser(int id) async {\n    try {\n      \u002F\u002F Simuliamo una chiamata di rete\n      await Future.delayed(const Duration(seconds: 1));\n\n      if (id \u003C= 0) {\n        return const Left(NotFoundFailure('Utente inesistente'));\n      }\n\n      final json = {\n        'id': id,\n        'name': 'Mario Rossi',\n        'email': 'mario.rossi@example.com',\n      };\n\n      return Right(User.fromJson(json));\n    } on FormatException {\n      return const Left(ServerFailure('Risposta non valida'));\n    } catch (_) {\n      return const Left(NetworkFailure());\n    }\n  }\n}","Il repository non lancia più eccezioni verso l'esterno: restituisce sempre un `Either\u003CFailure, User>`.",{"id":55,"position":56,"title":57,"body":58,"code_snippet":59,"code_language":39,"expected_result":60,"demo_url":10,"video_url":10},387,5,"Consumare Either con fold","Il metodo `fold` di `Either` prende due funzioni: la prima gestisce il caso `Left` (errore), la seconda il caso `Right` (successo). Il compilatore ti costringe a gestire entrambi, quindi non puoi dimenticare l'errore.\n\nVediamo un esempio d'uso in una semplice funzione.","Future\u003Cvoid> caricaUtente() async {\n  final repo = UserRepository();\n  final risultato = await repo.getUser(1);\n\n  risultato.fold(\n    (failure) => print('Errore: ${failure.message}'),\n    (user) => print('Benvenuto ${user.name}'),\n  );\n}","Chiamando `caricaUtente()` verrà stampato 'Benvenuto Mario Rossi', oppure il messaggio d'errore se qualcosa va storto.",{"id":62,"position":63,"title":64,"body":65,"code_snippet":66,"code_language":39,"expected_result":67,"demo_url":10,"video_url":10},388,6,"Mostrare il risultato nella UI","Integriamo il tutto in un widget. Usiamo un `FutureBuilder` per attendere il risultato e `fold` per decidere quale widget mostrare in base a successo o errore.\n\nQuesto pattern rende la UI dichiarativa e completamente sicura rispetto agli errori.","class UserPage extends StatelessWidget {\n  const UserPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final repo = UserRepository();\n\n    return Scaffold(\n      appBar: AppBar(title: const Text('Profilo utente')),\n      body: FutureBuilder\u003CEither\u003CFailure, User>>(\n        future: repo.getUser(1),\n        builder: (context, snapshot) {\n          if (!snapshot.hasData) {\n            return const Center(child: CircularProgressIndicator());\n          }\n\n          return snapshot.data!.fold(\n            (failure) => Center(\n              child: Text(\n                failure.message,\n                style: const TextStyle(color: Colors.red),\n              ),\n            ),\n            (user) => Center(\n              child: Column(\n                mainAxisAlignment: MainAxisAlignment.center,\n                children: [\n                  Text(user.name, style: Theme.of(context).textTheme.headlineSmall),\n                  Text(user.email),\n                ],\n              ),\n            ),\n          );\n        },\n      ),\n    );\n  }\n}","La pagina mostra lo spinner durante il caricamento, poi il nome e l'email dell'utente in caso di successo o un messaggio rosso in caso di errore.",{"id":69,"position":70,"title":71,"body":72,"code_snippet":73,"code_language":39,"expected_result":74,"demo_url":10,"video_url":10},389,7,"Trasformare i valori con map e getOrElse","`Either` offre metodi comodi per lavorare con i valori senza sempre usare `fold`.\n\n- `map`: trasforma il valore `Right` mantenendo intatto il `Left`.\n- `getOrElse`: restituisce il valore di successo o un default in caso di errore.\n- `isRight` \u002F `isLeft`: controlli booleani rapidi.\n\nQuesti helper rendono il codice più conciso quando devi solo trasformare i dati.","final risultato = await UserRepository().getUser(1);\n\n\u002F\u002F Trasformo il User in una stringa, solo se è un Right\nfinal Either\u003CFailure, String> nomeMaiuscolo =\n    risultato.map((user) => user.name.toUpperCase());\n\n\u002F\u002F Ottengo un valore di default in caso di errore\nfinal String nome = risultato\n    .map((u) => u.name)\n    .getOrElse(() => 'Ospite');\n\nprint(nome); \u002F\u002F 'Mario Rossi' oppure 'Ospite'","Puoi trasformare e leggere i valori in modo fluido, gestendo il caso d'errore con un default senza scrivere try\u002Fcatch.",[],1789120603090]