Flutter中QuerySnapshot.getDocuments().then未执行,Firebase取数空值报错
Hey there, let’s troubleshoot that frustrating Firebase data fetching issue you’re facing—where the .then() callback isn’t firing, you’re getting null values, and you’re using Provider for state management. I’ll break down common fixes and share optimized code for your four files, plus some pro tips.
1. First, Diagnose Why .then() Isn’t Executing
The most common culprits here are:
- Firebase Security Rules Blocking Reads: If your Firestore rules don’t allow unauthenticated reads (or your user isn’t authenticated), the
getDocuments()/get()call will fail silently unless you handle errors. - Missing Error Handling: Without a
.catchError()(or try-catch with async/await), failed requests won’t alert you to what’s wrong. - Outdated Firebase API:
getDocuments()is deprecated—using the newerget()method avoids hidden bugs from deprecated code.
2. Fixed & Optimized Code for Your Files
Let’s go through each file with targeted fixes:
ServicesCategories.dart (Data Layer)
Replace the outdated API, add error handling, and use async/await for cleaner, more readable code:
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:your_app/models/ModelCategories.dart'; class CategoryService { final FirebaseFirestore _db = FirebaseFirestore.instance; // Use async/await instead of .then() for better flow control Future<List<Category>> fetchCategories() async { try { // Replace deprecated getDocuments() with the official get() method final QuerySnapshot snapshot = await _db.collection('categories').get(); if (snapshot.docs.isEmpty) { print("⚠️ No categories found in Firestore"); return []; } // Map Firestore documents to Category models, with null safety return snapshot.docs.map((doc) { final data = doc.data() as Map<String, dynamic>; return Category.fromMap(data, doc.id); }).toList(); } catch (e) { print("❌ Error fetching categories: $e"); rethrow; // Pass error up to Provider to handle and display to UI } } }
ModelCategories.dart (Data Model)
Add null safety and default values to prevent null errors from missing Firestore fields:
class Category { final String id; final String name; final String imageUrl; Category({ required this.id, required this.name, required this.imageUrl, }); // Factory method with null-coalescing to handle missing fields factory Category.fromMap(Map<String, dynamic> map, String docId) { return Category( id: docId, name: map['name'] ?? 'Unnamed Category', // Fallback for missing name imageUrl: map['imageUrl'] ?? '', // Fallback for missing image URL ); } // Optional: Convert model back to Map for writing to Firestore Map<String, dynamic> toMap() => { 'name': name, 'imageUrl': imageUrl, }; }
Provider.dart (State Management)
Add loading/error states, properly notify listeners, and handle async operations safely:
import 'package:flutter/foundation.dart'; import 'package:your_app/services/ServicesCategories.dart'; import 'package:your_app/models/ModelCategories.dart'; class CategoryProvider with ChangeNotifier { List<Category> _categories = []; bool _isLoading = false; String? _errorMessage; // Getters for UI to access state safely List<Category> get categories => _categories; bool get isLoading => _isLoading; String? get errorMessage => _errorMessage; final CategoryService _categoryService = CategoryService(); Future<void> loadCategories() async { // Update state to loading and notify UI immediately _isLoading = true; _errorMessage = null; notifyListeners(); try { _categories = await _categoryService.fetchCategories(); } catch (e) { // Capture error and reset categories on failure _errorMessage = e.toString(); _categories = []; } finally { // End loading state and notify UI of final updates _isLoading = false; notifyListeners(); } } }
WidgetCategories.dart (UI Layer)
Listen to Provider state, handle loading/error/empty states, and trigger data fetch correctly:
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:your_app/providers/Provider.dart'; import 'package:your_app/models/ModelCategories.dart'; class CategoriesWidget extends StatelessWidget { @override Widget build(BuildContext context) { // Trigger data fetch once after the widget finishes building (avoids repeated calls) WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of<CategoryProvider>(context, listen: false).loadCategories(); }); return Consumer<CategoryProvider>( builder: (context, provider, child) { // Show loading indicator while fetching data if (provider.isLoading) { return const Center(child: CircularProgressIndicator()); } // Show error message if fetch failed if (provider.errorMessage != null) { return Center(child: Text('Error: ${provider.errorMessage}')); } // Show empty state if no categories exist in Firestore if (provider.categories.isEmpty) { return const Center(child: Text('No categories available')); } // Render the category list return ListView.builder( itemCount: provider.categories.length, itemBuilder: (context, index) { final category = provider.categories[index]; return ListTile( leading: Image.network( category.imageUrl, errorBuilder: (context, error, stackTrace) => const Icon(Icons.image_not_supported), ), title: Text(category.name), ); }, ); }, ); } }
3. Additional Optimization Tips
- Secure Firestore Rules: Don’t leave rules open permanently. For authenticated users, update your rules to:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /categories/{category} { allow read: if request.auth != null; allow write: if request.auth != null; } } } - Enable Offline Persistence: Let users access cached data when offline with:
await FirebaseFirestore.instance.enablePersistence(); - Avoid Unnecessary Rebuilds: Use
listen: falsewhen calling Provider methods that don’t need to trigger UI rebuilds (likeloadCategories()). - Use StatefulWidget for One-Time Calls: If you prefer, move the
loadCategories()call to a StatefulWidget’sinitState()instead ofaddPostFrameCallback().
内容的提问来源于stack exchange,提问作者rayncorg

