Firebase Firestore Android Java – Guida Completa

A modern, clean hero image for a technical tutorial about Firebase Firestore on Android with Java. Features the Firebase logo with orange flame colors, Android robot icon, and Java code elements on a dark background with subtle geometric patterns. Professional and tech-focused style.

Guida completa a Firebase Firestore in Android con Java

Questa guida su Firebase Firestore Android Java spiega come usare Firebase Cloud Firestore in un’app Android scritta in Java: differenze con Realtime Database, configurazione del progetto, struttura di documenti e collezioni, operazioni CRUD, listener in tempo reale, query avanzate e gestione degli errori.

Cos’è Firebase Cloud Firestore per Android Java

Cloud Firestore è un database NoSQL su cloud, parte della piattaforma Firebase. I dati sono organizzati in collezioni (collections) che contengono documenti (documents), a loro volta formati da coppie chiave/valore (field). È progettato per essere scalabile, con sincronizzazione in tempo reale e supporto offline su dispositivi mobili.

Firestore vs Realtime Database

Sia Firestore che Realtime Database sono database di Firebase, ma hanno differenze importanti:

  • Modello dati: Realtime Database usa un grande JSON ad albero; Firestore usa collezioni e documenti, più facili da strutturare e scalare.
  • Query: Firestore offre query più potenti (filtri multipli, ordinamenti, cursori) mantenendo buone prestazioni con dataset grandi.
  • Scalabilità: Firestore scala automaticamente orizzontalmente ed è pensato per carichi elevati.
  • Prezzi: Firestore fattura per numero di letture/scritture/eliminazioni; Realtime Database principalmente per traffico di dati e storage.
  • Offline: entrambi supportano l’offline, ma Firestore ha una cache locale più evoluta.

Per la maggior parte dei nuovi progetti si preferisce Firestore, soprattutto quando servono query complesse e una struttura dati più chiara.

Configurazione del progetto Firebase Firestore per Android Java

Per usare Firestore in Android con Java segui questi passaggi.

  • Vai alla console Firebase e crea un nuovo progetto (o usa un progetto esistente).
  • Aggiungi un’app Android al progetto inserendo il package name (ad esempio com.example.myapp).
  • Scarica il file google-services.json e copialo nella cartella app/ del tuo progetto Android.
  • Abilita Firestore nella sezione “Firestore Database” della console, scegliendo la modalità di sicurezza iniziale (per sviluppo si usa spesso la modalità di test, ma in produzione devi impostare regole sicure).

Aggiunta delle dipendenze Gradle

Nel file build.gradle di livello progetto (solitamente build.gradle nella root), assicurati di avere il plugin di Google Services:

buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.4.1' // versione aggiornata
    }
}

Nel file build.gradle del modulo app aggiungi le dipendenze per Firebase BoM e Firestore, e applica il plugin:

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services'
}

dependencies {
    // BoM di Firebase per gestire le versioni delle librerie in modo coerente
    implementation platform("com.google.firebase:firebase-bom:33.1.2")

    // Firestore
    implementation "com.google.firebase:firebase-firestore"

    // (opzionale) Analytics o altre librerie Firebase
    implementation "com.google.firebase:firebase-analytics"
}

Sincronizza il progetto con Gradle. All’avvio dell’app, Firebase verrà inizializzato automaticamente se il file google-services.json è configurato correttamente.

Inizializzare Firestore in Android (Java)

Normalmente si ottiene un’istanza di Firestore dove serve, ad esempio in un’Activity o in un repository.

import com.google.firebase.firestore.FirebaseFirestore;

public class MainActivity extends AppCompatActivity {

    private FirebaseFirestore db;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Ottieni l'istanza di Firestore
        db = FirebaseFirestore.getInstance();
    }
}

Struttura: collezioni, documenti e campi

In Firestore i dati sono organizzati gerarchicamente:

  • Collezione (Collection): un insieme di documenti, ad esempio users o products.
  • Documento (Document): contiene dati strutturati in campi, ad esempio un utente con name, email, age.
  • Sottocollezioni (Subcollections): collezioni annidate dentro un documento, ad esempio users/{userId}/orders.

Un possibile percorso completo è ad esempio: users/{userId}/orders/{orderId}.

Modellare un oggetto Java per Firestore

È comodo creare una classe modello (POJO) che rappresenti i dati di un documento. Firestore può convertire automaticamente fra documenti e oggetti Java.

public class User {
    private String id;      // non obbligatorio: puoi tenerlo solo lato client
    private String name;
    private String email;
    private int age;

    // Costruttore vuoto richiesto da Firestore
    public User() {
    }

    public User(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    // Getter e setter
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Operazioni CRUD con Firebase Firestore in Android Java

Crea: aggiungere documenti

Puoi aggiungere un documento con ID generato automaticamente o specificare tu l’ID.

// Esempio: aggiungere un utente con ID automatico
private void addUserAutoId() {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    User user = new User("Mario Rossi", "mario.rossi@example.com", 30);

    db.collection("users")
            .add(user)
            .addOnSuccessListener(documentReference -> {
                // documentReference.getId() contiene l'ID generato
                Log.d("Firestore", "Utente aggiunto con ID: " + documentReference.getId());
            })
            .addOnFailureListener(e -> {
                Log.w("Firestore", "Errore durante l'aggiunta", e);
            });
}
// Esempio: aggiungere/aggiornare un utente con ID specifico
private void addUserWithId(String userId) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    User user = new User("Luigi Verdi", "luigi.verdi@example.com", 25);

    db.collection("users")
            .document(userId) // usa un ID definito da te
            .set(user)       // set sovrascrive il documento
            .addOnSuccessListener(aVoid -> {
                Log.d("Firestore", "Utente salvato con ID: " + userId);
            })
            .addOnFailureListener(e -> {
                Log.w("Firestore", "Errore durante il salvataggio", e);
            });
}

Leggi: ottenere documenti

Puoi leggere un singolo documento o un’intera collezione.

// Leggere un singolo documento per ID
private void getUserById(String userId) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    db.collection("users")
            .document(userId)
            .get()
            .addOnSuccessListener(documentSnapshot -> {
                if (documentSnapshot.exists()) {
                    User user = documentSnapshot.toObject(User.class);
                    if (user != null) {
                        user.setId(documentSnapshot.getId());
                        Log.d("Firestore", "Nome utente: " + user.getName());
                    }
                } else {
                    Log.d("Firestore", "Documento non trovato");
                }
            })
            .addOnFailureListener(e -> {
                Log.w("Firestore", "Errore durante la lettura", e);
            });
}
// Leggere tutti i documenti di una collezione
private void getAllUsers() {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    db.collection("users")
            .get()
            .addOnSuccessListener(queryDocumentSnapshots -> {
                List<User> userList = new ArrayList<>();
                for (DocumentSnapshot doc : queryDocumentSnapshots) {
                    User user = doc.toObject(User.class);
                    if (user != null) {
                        user.setId(doc.getId());
                        userList.add(user);
                    }
                }

                Log.d("Firestore", "Numero di utenti: " + userList.size());
            })
            .addOnFailureListener(e -> {
                Log.w("Firestore", "Errore durante la lettura", e);
            });
}

Aggiorna: modificare campi di un documento

Per aggiornare solo alcuni campi usa il metodo update(), che non sovrascrive l’intero documento.

// Aggiornare uno o più campi di un utente
private void updateUserEmail(String userId, String newEmail) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    db.collection("users")
            .document(userId)
            .update("email", newEmail)
            .addOnSuccessListener(aVoid -> {
                Log.d("Firestore", "Email aggiornata");
            })
            .addOnFailureListener(e -> {
                Log.w("Firestore", "Errore durante l'aggiornamento", e);
            });
}

Per aggiornare più campi insieme puoi passare una mappa di valori:

private void updateUserFields(String userId) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    Map<String, Object> updates = new HashMap<>();
    updates.put("name", "Nuovo Nome");
    updates.put("age", 35);

    db.collection("users")
            .document(userId)
            .update(updates)
            .addOnSuccessListener(aVoid -> Log.d("Firestore", "Utente aggiornato"))
            .addOnFailureListener(e -> Log.w("Firestore", "Errore di aggiornamento", e));
}

Elimina: cancellare documenti e campi

Per eliminare un intero documento usa delete(). Puoi anche eliminare solo un campo specifico.

// Eliminare un documento utente
private void deleteUser(String userId) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    db.collection("users")
            .document(userId)
            .delete()
            .addOnSuccessListener(aVoid -> Log.d("Firestore", "Utente eliminato"))
            .addOnFailureListener(e -> Log.w("Firestore", "Errore durante l'eliminazione", e));
}
// Eliminare solo un campo (ad esempio "age")
private void deleteUserAgeField(String userId) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    Map<String, Object> updates = new HashMap<>();
    updates.put("age", FieldValue.delete());

    db.collection("users")
            .document(userId)
            .update(updates)
            .addOnSuccessListener(aVoid -> Log.d("Firestore", "Campo age eliminato"))
            .addOnFailureListener(e -> Log.w("Firestore", "Errore durante l'eliminazione del campo", e));
}

Listener in tempo reale con Firebase Firestore su Android

Una delle caratteristiche principali di Firestore è la sincronizzazione in tempo reale tramite listener. Puoi ascoltare le modifiche di un singolo documento o di una query su una collezione.

// Ascoltare i cambiamenti di un singolo documento utente
private ListenerRegistration userListener;

private void startUserListener(String userId) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    userListener = db.collection("users")
            .document(userId)
            .addSnapshotListener((snapshot, e) -> {
                if (e != null) {
                    Log.w("Firestore", "Errore nel listener", e);
                    return;
                }

                if (snapshot != null && snapshot.exists()) {
                    User user = snapshot.toObject(User.class);
                    if (user != null) {
                        user.setId(snapshot.getId());
                        Log.d("Firestore", "Dati utente aggiornati: " + user.getName());
                        // Aggiorna la UI qui
                    }
                } else {
                    Log.d("Firestore", "Documento utente non esiste più");
                }
            });
}

// Ricorda di rimuovere il listener quando non serve più (es. onStop)
private void stopUserListener() {
    if (userListener != null) {
        userListener.remove();
        userListener = null;
    }
}
// Ascoltare i cambiamenti di tutti gli utenti con una query
private ListenerRegistration usersListener;

private void startUsersListener() {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    usersListener = db.collection("users")
            .orderBy("name")
            .addSnapshotListener((querySnapshot, e) -> {
                if (e != null) {
                    Log.w("Firestore", "Errore nel listener collezione", e);
                    return;
                }

                if (querySnapshot != null) {
                    List<User> users = new ArrayList<>();
                    for (DocumentSnapshot doc : querySnapshot) {
                        User user = doc.toObject(User.class);
                        if (user != null) {
                            user.setId(doc.getId());
                            users.add(user);
                        }
                    }
                    Log.d("Firestore", "Numero utenti aggiornato: " + users.size());
                    // Aggiorna la lista nella UI
                }
            });
}

private void stopUsersListener() {
    if (usersListener != null) {
        usersListener.remove();
        usersListener = null;
    }
}

Query e filtri in Firebase Firestore Android Java

Firestore supporta varie operazioni di query: filtri, ordinamenti, limiti e combinazioni. Alcune query richiedono indici compositi, che la console ti suggerirà di creare.

// Utenti con età maggiore o uguale a 18
private void getAdultUsers() {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    db.collection("users")
            .whereGreaterThanOrEqualTo("age", 18)
            .get()
            .addOnSuccessListener(querySnapshot -> {
                for (DocumentSnapshot doc : querySnapshot) {
                    User user = doc.toObject(User.class);
                    if (user != null) {
                        Log.d("Firestore", "Adulto: " + user.getName());
                    }
                }
            })
            .addOnFailureListener(e -> Log.w("Firestore", "Errore query", e));
}
// Utenti con email che inizia con una certa stringa (ricerca semplice con range)
private void searchUsersByNamePrefix(String prefix) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    // Per ricerche "inizia con" si usa un trucco con startAt/endAt
    db.collection("users")
            .orderBy("name")
            .startAt(prefix)
            .endAt(prefix + "\uf8ff")
            .get()
            .addOnSuccessListener(querySnapshot -> {
                for (DocumentSnapshot doc : querySnapshot) {
                    User user = doc.toObject(User.class);
                    if (user != null) {
                        Log.d("Firestore", "Trovato: " + user.getName());
                    }
                }
            })
            .addOnFailureListener(e -> Log.w("Firestore", "Errore ricerca", e));
}
// Combina filtri, ordinamento e limite
private void getYoungUsersOrdered() {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    db.collection("users")
            .whereLessThan("age", 30)
            .orderBy("age")    // devi ordinare per lo stesso campo usato nel filtro range
            .limit(10)
            .get()
            .addOnSuccessListener(querySnapshot -> {
                for (DocumentSnapshot doc : querySnapshot) {
                    User user = doc.toObject(User.class);
                    if (user != null) {
                        Log.d("Firestore", user.getName() + " (" + user.getAge() + ")");
                    }
                }
            })
            .addOnFailureListener(e -> Log.w("Firestore", "Errore query combinata", e));
}

Transazioni e scritture in batch (cenni)

Per garantire consistenza tra più operazioni di scrittura puoi usare le transazioni o le scritture in batch.

// Esempio di transazione: incrementare in modo atomico l'età di un utente
private void incrementUserAge(String userId) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    DocumentReference userRef = db.collection("users").document(userId);

    db.runTransaction(transaction -> {
        DocumentSnapshot snapshot = transaction.get(userRef);
        Long age = snapshot.getLong("age");
        if (age == null) {
            age = 0L;
        }
        transaction.update(userRef, "age", age + 1);
        return null;
    }).addOnSuccessListener(aVoid -> Log.d("Firestore", "Età incrementata"))
      .addOnFailureListener(e -> Log.w("Firestore", "Errore transazione", e));
}

Le scritture in batch permettono di eseguire più operazioni (set, update, delete) in un’unica chiamata atomica.

private void batchWriteExample() {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    WriteBatch batch = db.batch();

    DocumentReference user1 = db.collection("users").document();
    DocumentReference user2 = db.collection("users").document();

    batch.set(user1, new User("Anna", "anna@example.com", 28));
    batch.set(user2, new User("Paolo", "paolo@example.com", 32));

    batch.commit()
            .addOnSuccessListener(aVoid -> Log.d("Firestore", "Batch completato"))
            .addOnFailureListener(e -> Log.w("Firestore", "Errore batch", e));
}

Gestione degli errori

La gestione degli errori in Firestore si basa principalmente sugli OnFailureListener e sugli errori nei listener in tempo reale. Alcune buone pratiche:

  • Log dettagliati: usa Log.w() e Log.e() per registrare errori, includendo lo stack trace.
  • Feedback all’utente: mostra messaggi chiari (Toast, Snackbar, UI) quando un’operazione fallisce.
  • Retry: valuta se riprovare automaticamente in caso di errori di rete temporanei.
  • Validazione lato client: controlla i dati prima di inviarli (campi obbligatori, formati corretti).
  • Regole di sicurezza: errori PERMISSION_DENIED indicano regole Firestore che bloccano l’operazione; verifica e aggiorna le regole nella console Firebase.
// Esempio generico di gestione errore con messaggio utente
private void safeAddUser(User user, Context context) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    db.collection("users")
            .add(user)
            .addOnSuccessListener(ref -> {
                Toast.makeText(context, "Utente salvato", Toast.LENGTH_SHORT).show();
            })
            .addOnFailureListener(e -> {
                Log.e("Firestore", "Impossibile salvare l'utente", e);
                Toast.makeText(context, "Errore durante il salvataggio. Riprova.", Toast.LENGTH_LONG).show();
            });
}

Supporto offline

Firestore per Android abilita di default la persistenza offline: le scritture vengono messe in coda e sincronizzate quando la connessione torna disponibile, e le letture usano la cache locale se il server non è raggiungibile. Puoi personalizzare questo comportamento tramite le impostazioni, ma per molte app le impostazioni predefinite sono sufficienti.

Conclusione

Firebase Cloud Firestore offre un modo flessibile e scalabile per gestire dati in tempo reale nelle app Android. Con una buona modellazione di collezioni e documenti, un uso corretto di listener, query e transazioni, puoi costruire applicazioni reattive e affidabili con Firebase Firestore Android Java. Puoi estendere gli esempi di questa guida integrandoli con la tua architettura (MVVM, Repository, ecc.) e con altre funzionalità di Firebase come Authentication e Cloud Functions.

Guide correlate

Commenti

Rispondi

Scopri di più da App Tutorial

Abbonati ora per continuare a leggere e avere accesso all'archivio completo.

Continua a leggere