Salvare e leggere file JSON nel dispositivo con Flutter e path_provider
GuideIntermedio30 min Flutter 3.x

Salvare e leggere file JSON nel dispositivo con Flutter e path_provider

In molte app hai bisogno di salvare dati strutturati che sopravvivano alla chiusura dell'app, ma senza la complessità di un database. In questo tutorial vedremo come serializzare oggetti Dart in file JSON e salvarli nella directory dei documenti dell'applicazione usando path_provider e dart:io.

Realizzeremo una piccola app di gestione note che salva una lista di oggetti su disco e la ricarica all'avvio. Imparerai a:

  • ottenere il percorso corretto per i file dell'app
  • serializzare e deserializzare oggetti in JSON
  • creare una classe di storage riutilizzabile
  • gestire il caso di file inesistente

È un pattern leggero, perfetto per configurazioni, cache di dati e piccole collezioni.

  1. 1

    Aggiungere la dipendenza path_provider

    Il pacchetto path_provider fornisce metodi cross-platform per ottenere i percorsi delle directory di sistema (documenti, cache, temp). Aggiungilo al progetto.

    Esegui il comando da terminale oppure aggiungilo manualmente al pubspec.yaml.

    flutter pub add path_provider

    Risultato atteso

    Nel pubspec.yaml compare la dipendenza path_provider e `flutter pub get` viene eseguito con successo.

  2. 2

    Creare il modello dati serializzabile

    Definiamo il modello Note con i metodi toJson e fromJson per convertire l'oggetto da e verso una Map. Questa struttura è essenziale per la serializzazione JSON.

    class Note {
      final String id;
      final String title;
      final String content;
    
      Note({required this.id, required this.title, required this.content});
    
      Map<String, dynamic> toJson() => {
            'id': id,
            'title': title,
            'content': content,
          };
    
      factory Note.fromJson(Map<String, dynamic> json) => Note(
            id: json['id'] as String,
            title: json['title'] as String,
            content: json['content'] as String,
          );
    }

    Risultato atteso

    Hai una classe Note in grado di convertirsi in Map e di essere ricostruita da una Map.

  3. 3

    Ottenere il percorso del file

    Usiamo getApplicationDocumentsDirectory() di path_provider per ottenere la directory in cui salvare i dati persistenti dell'app. Costruiamo poi un riferimento al file notes.json.

    Questa directory è privata all'app e non viene cancellata dal sistema, a differenza della cache.

    import 'dart:io';
    import 'package:path_provider/path_provider.dart';
    
    class NotesStorage {
      Future<File> _getFile() async {
        final dir = await getApplicationDocumentsDirectory();
        return File('${dir.path}/notes.json');
      }
    }

    Risultato atteso

    Il metodo _getFile restituisce un riferimento al file notes.json nella directory dei documenti.

  4. 4

    Scrivere la lista di note su file

    Serializziamo la lista di Note in una stringa JSON con jsonEncode e la scriviamo sul file con writeAsString. Il metodo crea o sovrascrive il file esistente.

    import 'dart:convert';
    
    Future<void> saveNotes(List<Note> notes) async {
      final file = await _getFile();
      final jsonList = notes.map((n) => n.toJson()).toList();
      await file.writeAsString(jsonEncode(jsonList));
    }

    Risultato atteso

    Chiamando saveNotes, la lista viene scritta come array JSON nel file notes.json.

  5. 5

    Leggere le note dal file

    Alla lettura dobbiamo gestire il caso in cui il file non esista ancora (primo avvio): in quel caso restituiamo una lista vuota. Altrimenti decodifichiamo il contenuto con jsonDecode e ricostruiamo gli oggetti.

    Future<List<Note>> loadNotes() async {
      try {
        final file = await _getFile();
        if (!await file.exists()) return [];
    
        final content = await file.readAsString();
        if (content.trim().isEmpty) return [];
    
        final List<dynamic> jsonList = jsonDecode(content);
        return jsonList
            .map((e) => Note.fromJson(e as Map<String, dynamic>))
            .toList();
      } catch (e) {
        // In caso di file corrotto ripartiamo da lista vuota
        return [];
      }
    }

    Risultato atteso

    loadNotes restituisce la lista salvata, oppure una lista vuota se il file non esiste o è corrotto.

  6. 6

    Usare lo storage in un widget

    Integriamo la classe in una schermata: carichiamo le note all'avvio con initState e le aggiorniamo salvandole quando aggiungiamo un elemento. Usiamo un FutureBuilder o uno stato locale per mostrarle.

    class NotesScreen extends StatefulWidget {
      const NotesScreen({super.key});
      @override
      State<NotesScreen> createState() => _NotesScreenState();
    }
    
    class _NotesScreenState extends State<NotesScreen> {
      final _storage = NotesStorage();
      List<Note> _notes = [];
    
      @override
      void initState() {
        super.initState();
        _load();
      }
    
      Future<void> _load() async {
        final notes = await _storage.loadNotes();
        setState(() => _notes = notes);
      }
    
      Future<void> _addNote() async {
        final note = Note(
          id: DateTime.now().millisecondsSinceEpoch.toString(),
          title: 'Nuova nota',
          content: 'Contenuto di esempio',
        );
        setState(() => _notes.add(note));
        await _storage.saveNotes(_notes);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Note')), 
          floatingActionButton: FloatingActionButton(
            onPressed: _addNote,
            child: const Icon(Icons.add),
          ),
          body: ListView.builder(
            itemCount: _notes.length,
            itemBuilder: (context, i) => ListTile(
              title: Text(_notes[i].title),
              subtitle: Text(_notes[i].content),
            ),
          ),
        );
      }
    }

    Risultato atteso

    L'app mostra la lista di note, ne aggiunge di nuove al tap sul FAB e le ricarica correttamente dopo il riavvio.

  7. 7

    Testare la persistenza

    Per verificare che tutto funzioni: avvia l'app, aggiungi alcune note, chiudi completamente l'applicazione (non solo in background) e riaprila.

    Suggerimento: su Android puoi ispezionare il file salvato tramite Android Studio > Device File Explorer nel percorso data/data/<package>/app_flutter/notes.json. Ricordati che questo approccio è ideale per collezioni piccole; per grandi volumi o query complesse preferisci un database come SQLite.

    Risultato atteso

    Dopo la riapertura dell'app le note aggiunte in precedenza sono ancora presenti, confermando la persistenza su file JSON.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!