Mastering Caching & Network Optimization in Flutter with Firestore

Mastering Caching & Network Optimization in Flutter with Firestore

TB

Teqani Blogs

Writer at Teqani

June 18, 20253 min read

When building Flutter apps that rely on Firestore, performance and cost optimization become critical. This article explores various strategies to improve efficiency and reduce Firebase costs, ensuring a smooth user experience even on poor networks.

Firestore’s Built-In Offline Persistence

Firestore provides built-in disk caching to keep data locally cached and support offline access, reducing repeated reads. To enable it:

FirebaseFirestore.instance.settings = const Settings(
 persistenceEnabled: true,
 cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);

Benefits:

  • Keeps data locally cached
  • Supports offline access
  • Reduces repeated reads

Pitfall: This cache may evict after ~30 minutes of inactivity or space limits.

To force cache reads:

final doc = await FirebaseFirestore.instance
 .collection('settings')
 .doc('appInfo')
 .get(const GetOptions(source: Source.cache));

Advanced Caching with Hive

For persistent control over frequently used documents, use Hive:

Future<Map<String, dynamic>?> getUserProfile(String uid) async {
 final box = await Hive.openBox('userBox');
 final cached = box.get('user_$uid');
 if (cached != null) return Map.from(cached);
 final doc = await FirebaseFirestore.instance.collection('users').doc(uid).get();
 box.put('user_$uid', doc.data());
 return doc.data();
}

Efficient Pagination with Cursors

Avoid offset-based pagination, which wastes reads:

List<DocumentSnapshot> fetched = [];
DocumentSnapshot? lastDoc;

Future<void> loadPage({bool next = false}) async {
 var query = FirebaseFirestore.instance
 .collection('items')
 .orderBy('createdAt', descending: true)
 .limit(20);

 if (next && lastDoc != null) {
 query = query.startAfterDocument(lastDoc!);
 }

 final snap = await query.get();
 if (next) fetched.addAll(snap.docs);
 else fetched = snap.docs;
 lastDoc = snap.docs.isNotEmpty ? snap.docs.last : null;
}

ListView.builder(
 itemCount: fetched.length + 1,
 itemBuilder: (ctx, i) {
 if (i == fetched.length) {
 loadPage(next: true);
 return Center(child: CircularProgressIndicator());
 }

 final data = fetched[i].data() as Map;
 return ListTile(title: Text(data['title']));
 },
)

Combine Firestore + Hive for Offline Pagination

Future<void> loadMixedPage() async {
 final box = await Hive.openBox('pageBox');
 lastDoc ??= box.get('lastDoc');

 final snap = await FirebaseFirestore.instance
 .collection('items')
 .orderBy('createdAt')
 .startAfterDocument(lastDoc!)
 .limit(5)
 .get();

 final cached = box.get('cachedItems') as List<Map>? ?? [];
 final newDocs = snap.docs.map((d) => d.data()).toList();
 box.put('cachedItems', [...cached, ...newDocs]);
 box.put('lastDoc', snap.docs.last);
}

Optimization Tips

  • Use .orderBy('createdAt') with indexed fields
  • Avoid excessive snapshot listeners (use .get() when possible)
  • Monitor with Firebase Dashboard or DevTools
  • Shard large documents if needed
TB

Teqani Blogs

Verified
Writer at Teqani

Senior Software Engineer with 10 years of experience

June 18, 2025
Teqani Certified

All blogs are certified by our company and reviewed by our specialists
Issue Number: #d042cfac-84ba-4cdf-bebb-16346ae8aa9e