In Flutter's cloud_firestore, how do Settings.persistenceEnabled and cacheSizeBytes shape offline behaviour, and when must those settings be applied?
answer
- assign before any other call
- on-disk versus in-memory cache
- cleanup threshold, not a cap
- CACHE_SIZE_UNLIMITED is -1
- web defaults to memory
basics
~10 spersistenceEnabled chooses an on-disk or in-memory cache, and cacheSizeBytes sets the size that triggers cleanup of little-used data. Assign FirebaseFirestore.instance.settings before any other Firestore call; web uses memory unless persistence is enabled.
solid answer
~40 s`FirebaseFirestore.instance.settings = Settings(...)` configures the local cache, and its documentation requires setting it before invoking any other method on the instance, because the native instance is created with its settings on first use. `persistenceEnabled: true` asks for an on-disk cache that survives restarts; `false` keeps it in memory. `cacheSizeBytes` is an approximate threshold: past it, the SDK removes data that has not been used recently. The documented default is 40 MB, the minimum 1 MB, and `Settings.CACHE_SIZE_UNLIMITED` (-1) disables garbage collection. Leaving `persistenceEnabled` null lets the native SDK default apply on Android and iOS, while the web plugin then uses a memory cache lost on reload. With persistence enabled, the pinned Android code maps an omitted or unlimited size to 100 MB, so pass an explicit size when it matters.
code
dart · 24 linesimport 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// Before any other Firestore call.
FirebaseFirestore.instance.settings = const Settings(
persistenceEnabled: true,
cacheSizeBytes: 50 * 1024 * 1024,
);
runApp(const TripPlannerApp());
}
class TripPlannerApp extends StatelessWidget {
const TripPlannerApp({super.key});
@override
Widget build(BuildContext context) =>
const MaterialApp(home: Scaffold(body: Text('Trips')));
}go deeper
Know that Firestore keeps a local cache for offline use, configured through FirebaseFirestore.instance.settings.
Explain persistenceEnabled and cacheSizeBytes, the set-before-use rule, and how the web default differs from mobile.
Size the cache for the real working set, handle sensitive data on shared devices, and know the source-level quirks of unlimited sizes.
Decide the offline contract per platform — what must work without a network and what may be lost — and configure caching to match it.
## Why a trip planner cares about the cache Travellers open the group-trip planner on trains, in airports and abroad without roaming. `cloud_firestore` keeps a local cache of documents it has seen and queues writes made while offline, which is what lets the itinerary still open and edits still stick without a connection. How that cache behaves is configured through `Settings`. ## The two settings `Settings` is an immutable class in the pinned plugin. The two fields that matter for offline behaviour: - **`persistenceEnabled`** (`bool?`) — "attempts to enable persistent storage". `true` asks for an on-disk cache that survives restarts; `false` asks for an in-memory cache only. - **`cacheSizeBytes`** (`int?`) — "an approximate cache size threshold for the on-disk data". When the cache grows beyond it, the SDK starts removing data that has not been used recently. It is a trigger for cleanup, not a hard cap. The doc comment names a default of 40 MB, a minimum of 1 MB, and **`Settings.CACHE_SIZE_UNLIMITED`** (the constant `-1`) to disable garbage collection. The Dart side also asserts that any explicit value lies between 1,048,576 and 104,857,600 bytes, or is `CACHE_SIZE_UNLIMITED`. ## Applying them ```dart FirebaseFirestore.instance.settings = const Settings( persistenceEnabled: true, cacheSizeBytes: 50 * 1024 * 1024, ); ``` The setter's documentation is explicit: **you must set these before invoking any other methods** on that `FirebaseFirestore` instance. The native side creates and caches the Firestore instance on first use, with whatever settings it had then; assigning settings afterwards does not reconfigure the cache that is already running. The practical rule is to assign them in `main`, right after `Firebase.initializeApp` and before any widget touches Firestore. ## Platform differences | Platform | `persistenceEnabled` left `null` | Notes | |---|---|---| | Android, iOS | No cache settings are passed, so the native Firestore SDK's own default applies — a persistent on-disk cache | Set it explicitly to make intent visible | | Web | The plugin configures an **in-memory** cache, lost on reload | Set `persistenceEnabled: true` for an IndexedDB-backed cache; `webPersistentTabManager` controls multi-tab behaviour | One more source-level detail worth knowing: when `persistenceEnabled` is `true` and `cacheSizeBytes` is omitted or `CACHE_SIZE_UNLIMITED`, the pinned Android code asks for a 100 MB cache (104,857,600 bytes) while the iOS code asks for an unlimited one. If the size matters, pass an explicit value. ## Choosing the values 1. **Keep persistence on for mobile** when offline use matters — it is what makes a trip's itinerary open on a plane. 2. **Turn it on for web deliberately**, knowing the browser stores the data and several tabs must coordinate. 3. **Size the cache for the working set**, not the whole database: the itineraries a user actually opens, plus room for thumbnails' metadata. 4. **Consider turning persistence off for sensitive data on shared devices.** The `clearPersistence()` documentation warns that clearing does not securely erase cached data and recommends not enabling persistence at all when disclosure between sessions matters. ## How an offline edit flows 1. A traveller edits the itinerary on a plane; the write lands in the local cache and is queued. 2. Listeners on that query fire at once, with `hasPendingWrites` set to `true`. 3. On reconnect, the SDK sends the queued write; with persistence enabled, the queue survived an app restart in between. 4. Once the server acknowledges it, listeners that asked for metadata changes see `hasPendingWrites` turn `false`. ## Related methods - `clearPersistence()` wipes the cache, including pending writes, but only while the instance is not running — before first use or after `terminate()`. It is aimed mainly at tests. - `terminate()` shuts the instance down; afterwards only `clearPersistence()` may be called. - `waitForPendingWrites()` resolves once all pending writes of the active user have been acknowledged — useful before signing out. ## Common mistakes - Assigning `settings` after a repository has already run its first query, then wondering why the cache size did not change. - Assuming web behaves like mobile and discovering on reload that the offline cache is gone. - Setting `CACHE_SIZE_UNLIMITED` and expecting unlimited storage on every platform. - Treating `cacheSizeBytes` as a hard ceiling rather than a cleanup threshold.
- How do you wipe the Firestore cache between users on a shared device?`clearPersistence()` clears cached documents and pending writes, but only while the instance is not running — before first use or after `terminate()`. Its documentation warns it does not securely erase data and recommends not enabling persistence at all where cached data must not leak between sessions.
- Why might a cache size change seem to have no effect?The settings were assigned after the app had already used Firestore, so the native instance was created with the old configuration. Assign `settings` in `main` before any repository runs its first query.
saying these in an interview costs you the question
- Firestore settings can be changed at any time and apply to the running instance.
- cacheSizeBytes is a hard limit the cache never exceeds.
- Web builds persist the Firestore cache to disk without any configuration.
- CACHE_SIZE_UNLIMITED means an unlimited cache on every platform in FlutterFire.
- clearPersistence() securely erases cached data while the app is running.