Flutter中ListView需热重载/刷新才显示且出现RangeError(index)问题求助
Hey there, let's tackle your two issues step by step. I've gone through your code and spotted several key problems causing both the ListView display delay and the RangeError.
1. Why You're Facing These Issues
RangeError Root Cause
You're using uniqueImages[index + 1] in the ListView's image builder, but uniqueImages only gets populated when the CarouselSlider's itemBuilder runs. Since you have two separate FutureBuilders making duplicate API calls, the ListView's data might load before the Carousel's, leaving uniqueImages empty. Plus, modifying state variables like urlImages directly in widget build methods leads to inconsistent list lengths and invalid index access.
ListView Hot Reload Requirement
- You're calling
apiCall()twice (once for Carousel, once for ListView), creating separate network requests that resolve at different times. - The
Futurein eachFutureBuilderis recreated every time the widget rebuilds (e.g., whensetStateruns), causing snapshot state chaos and making the ListView only render reliably after a hot reload. - Modifying class-level lists like
growableListinside the ListView's itemBuilder triggers unnecessary rebuilds and state inconsistencies.
Fixed Code Implementation
import 'package:cached_network_image/cached_network_image.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:video_games/model.dart'; import 'package:flutter/material.dart'; import 'package:video_games/send_models.dart'; import 'package:video_games/views/details_view.dart'; import 'package:get/get.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; class HomePage extends StatefulWidget { HomePage({ Key? key, }) : super(key: key); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { // Store the API future once to avoid duplicate requests late Future<Autogenerated> _gamesFuture; int slideractiveindex = 0; @override void initState() { super.initState(); // Initialize the API request once when the widget loads _gamesFuture = apiCall(); // Clear cache only once on initialization clearCache(); } Future<Autogenerated> apiCall() async { final response = await http.get(Uri.parse( 'https://api.rawg.io/api/games?key=5ac29048d12d45d0949c77038115cb56&page=1')); return Autogenerated.fromJson(jsonDecode(response.body)); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: FutureBuilder<Autogenerated>( // Reuse the single stored future instead of creating new ones future: _gamesFuture, builder: (BuildContext context, AsyncSnapshot<Autogenerated> snapshot) { // Handle all possible snapshot states properly if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('Failed to load data: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data?.results == null) { return const Center(child: Text('No games found')); } final List<Results> games = snapshot.data!.results!; // Generate unique images once when data is ready final List<String?> uniqueImages = games .map((game) => game.backgroundImage) .where((image) => image != null) .toSet() .toList(); return Column( children: [ CarouselSlider.builder( // Don't render more slides than available images itemCount: uniqueImages.length.clamp(0, 20), itemBuilder: (context, index, realIndex) { final urlImage = uniqueImages[index]; return buildImage(urlImage, index); }, options: CarouselOptions( autoPlay: true, enlargeCenterPage: true, onPageChanged: (index, reason) {}), ), const SizedBox(height: 32), ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (BuildContext context, index) { final game = games[index]; // Use the game's own background image instead of guessing indices final urlImage = game.backgroundImage; return Padding( padding: const EdgeInsets.all(10.0), child: ListTile( tileColor: Colors.black12, leading: InkWell( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => DetailsPage( id: '${game.id}', ), )); }, child: buildImage2(urlImage, index)), title: Column( children: [ Text('${game.name}'), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Rating : ${game.rating}'), const SizedBox(width: 20), Text('Release : ${game.released}'), ], ) ], ), ), ); }, // Don't render more items than available games itemCount: games.length.clamp(0, 19), ), ], ); }, ), ); } Widget buildImage(String? urlImage, int index) { return Container( margin: const EdgeInsets.symmetric(horizontal: 12), color: Colors.grey, child: CachedNetworkImage( imageUrl: urlImage!, fit: BoxFit.cover, key: UniqueKey(), errorWidget: (context, url, error) => const Icon(Icons.error), placeholder: (context, url) => const Center(child: CircularProgressIndicator()), ), ); } Widget buildImage2(String? urlImage, int index) { return Container( height: 100, width: 100, child: CachedNetworkImage( imageUrl: urlImage!, key: UniqueKey(), fit: BoxFit.cover, errorWidget: (context, url, error) => const Icon(Icons.error), placeholder: (context, url) => const Center(child: CircularProgressIndicator()), ), ); } void clearCache() { DefaultCacheManager().emptyCache(); imageCache!.clear(); imageCache!.clearLiveImages(); } }
Key Fixes Breakdown
- Single API Request: We store the API future in
_gamesFutureand initialize it once ininitState(), so both the Carousel and ListView use the exact same data set with no duplicate requests. - Robust Snapshot Handling: We explicitly handle loading, error, and empty data states to prevent trying to render content before data is ready.
- Eliminate State Bugs: Removed messy class-level lists that were modified in build methods. Instead, we generate unique images once when data loads, and use each game's own background image directly in the ListView (no more invalid index guesswork).
- Prevent RangeErrors: Added
.clamp(0, ...)to item counts to ensure we never try to render more items than available data, and removed the riskyindex + 1offset. - Optimize Cache Clearing: Moved cache clearing to
initState()so it only runs once, avoiding unnecessary rebuilds and state inconsistencies.
内容的提问来源于stack exchange,提问作者demonjoseph
相关产品推荐
相关产品推荐

