Flutter中CustomScrollView与ScopedModel问题求助
Hey there! Glad you got the scroll issue sorted out—let's tackle that tricky double-rebuild and duplicate JSON data problem next. I've run into similar scoped_model quirks before, so here's what to check and fix:
First, Diagnose the Root Cause
Before jumping into fixes, let's narrow down why the page is rebuilding twice:
- Check if
notifyListeners()is being called more than necessary in yourRelationScopedModel—each call triggers a rebuild of widgets listening to the model. - Screen rotations (or any configuration change) cause Flutter to rebuild the widget tree by default. If your data-loading logic runs on every rebuild, you'll get duplicate entries.
- Search operations might be triggering multiple data fetches (e.g., if you're calling a load method on every text input without debouncing or checks).
Step-by-Step Fixes
1. Add Loading State to Your Scoped Model
Prevent duplicate data requests by adding an isLoading flag to your RelationScopedModel. This stops the model from fetching data while it's already in progress:
class RelationScopedModel extends Model { bool isLoading = false; List<YourDataModel> items = []; bool hasMorePages = true; bool atTheEnd = false; Future<void> loadData({String? searchQuery}) async { // Exit if we're already loading or no more pages exist if (isLoading || !hasMorePages) return; isLoading = true; notifyListeners(); // Notify UI we're starting to load try { // Fetch your JSON data—pass searchQuery if needed final newData = await fetchJsonData(searchQuery: searchQuery); // If it's a new search, clear existing items first if (searchQuery != null) { items.clear(); } if (newData.isNotEmpty) { items.addAll(newData); } else { hasMorePages = false; atTheEnd = true; } } catch (e) { // Handle errors (e.g., show snackbar) print('Error loading data: $e'); } finally { isLoading = false; notifyListeners(); // Notify UI loading is done } } void initializeValues() { atTheEnd = false; hasMorePages = true; // Only clear items if you need a full reset (e.g., on search start) // items.clear(); } }
2. Limit Data Loading Triggers in Your Screen
In your search_screen.dart, make sure you only trigger data loading when it's actually needed—don't run it on every rebuild. Use didChangeDependencies with checks:
class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @override State<SearchScreen> createState() => _SearchScreenState(); } class _SearchScreenState extends State<SearchScreen> { final TextEditingController _searchController = TextEditingController(); @override void didChangeDependencies() { super.didChangeDependencies(); final model = ScopedModel.of<RelationScopedModel>(context); // Only load data if items are empty AND we're not already loading if (model.items.isEmpty && !model.isLoading) { model.loadData(); } } @override Widget build(BuildContext context) { // Optional: Add a print to track rebuilds // print('SearchScreen rebuilt'); return ScopedModelDescendant<RelationScopedModel>( builder: (context, child, model) { return Scaffold( // Your UI code here—CustomScrollView, search bar, etc. body: CustomScrollView( slivers: [ // ... your slivers ], ), floatingActionButton: model.isLoading ? const CircularProgressIndicator() : FloatingActionButton( onPressed: () => model.loadData(searchQuery: _searchController.text), child: const Icon(Icons.search), ), ); }, ); } }
3. Handle Screen Rotations Properly
Flutter rebuilds widgets on configuration changes like rotation. To avoid reloading data unnecessarily:
- If your data is stored in the scoped model (which it should be), the model will retain the data across rebuilds—so as long as you don't re-run
loadData()on every rebuild, you won't get duplicates. - If you need to adjust UI for landscape/portrait, use
MediaQueryto detect orientation instead of relying on rebuild triggers.
4. Fix Search Logic to Avoid Duplicates
When performing a search, always clear existing items before adding new results (like we did in the loadData method above). This ensures old data doesn't mix with new search results. Also, consider adding a debounce to your search input to prevent multiple requests as the user types:
// Add this to your state class Timer? _debounceTimer; void _onSearchChanged(String query) { _debounceTimer?.cancel(); _debounceTimer = Timer(const Duration(milliseconds: 300), () { final model = ScopedModel.of<RelationScopedModel>(context, rebuildOnChange: false); model.initializeValues(); // Reset pagination model.loadData(searchQuery: query); }); } // Use this in your TextField TextField( controller: _searchController, onChanged: _onSearchChanged, decoration: const InputDecoration(hintText: 'Search...'), )
Verify the Fix
Add a print statement in your build method (like the commented one above) to track how many times the screen rebuilds. If you see it only rebuilds once per state change (not twice), you're on the right track. Also, check your items list after loading/searching to ensure no duplicate entries.
内容的提问来源于stack exchange,提问作者SJente

