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.jsone copialo nella cartellaapp/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
usersoproducts. - 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()eLog.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_DENIEDindicano 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.

Rispondi