Flutter处理Movie API空数据:基于Repo、Provider与MovieWidget实现随机有效电影推荐方案问询
Hey there! Let's work through this problem together. The main goal here is to make sure users never see a blank screen when they tap that recommend button—instead, we’ll keep fetching until we get a valid movie ID, all while working with your Repo, Provider, and MovieWidget setup. Here’s how to do it:
The repo is where we’ll implement the core retry logic. We’ll generate random IDs, check if the API returns valid data, and loop until we get a hit (or hit a max retry limit to avoid infinite loops).
import 'dart:math'; import 'package:flutter/foundation.dart'; class MovieRepo { final ApiService apiService; // Assume this is your existing API client MovieRepo(this.apiService); // Add max retries to prevent infinite loops if most IDs are invalid Future<Movie> getRandomMovie({int maxRetries = 10}) async { int attemptCount = 0; while (attemptCount < maxRetries) { try { // Generate your random MovieID exactly as you did before final randomId = Random().nextInt(100000) + 1; final movie = await apiService.fetchMovieById(randomId); // Validate the movie data (adjust this check based on your API's response structure) if (movie.id != null && movie.title.isNotEmpty) { return movie; // Success! Return the valid movie } } catch (e) { // Catch API errors like 404 (invalid ID) or network issues if (kDebugMode) { print("Failed attempt ${attemptCount + 1}: $e"); } } attemptCount++; // Add a tiny delay between retries to avoid spamming the API await Future.delayed(const Duration(milliseconds: 200)); } // If we exhaust all retries, throw an error to handle in the provider throw Exception("Couldn't find a valid movie after $maxRetries attempts"); } }
Next, we’ll update the provider to handle loading, success, and error states. This ensures the UI always shows feedback instead of a blank screen.
import 'package:flutter/material.dart'; class MovieProvider extends ChangeNotifier { final MovieRepo movieRepo; Movie? _currentMovie; bool _isLoading = false; String? _errorMessage; Movie? get currentMovie => _currentMovie; bool get isLoading => _isLoading; String? get errorMessage => _errorMessage; MovieProvider(this.movieRepo); Future<void> fetchRandomMovie() async { // Set loading state first so the UI shows a spinner _isLoading = true; _errorMessage = null; notifyListeners(); try { // Call our repo's retry-enabled method _currentMovie = await movieRepo.getRandomMovie(); } catch (e) { // Capture errors if all retries fail _errorMessage = e.toString(); } finally { // Turn off loading state regardless of success/error _isLoading = false; notifyListeners(); } } }
Finally, we’ll adjust the MovieWidget to react to the provider’s state. Users will see a loading spinner while fetching, valid movie data when it works, or an error message with a retry option if all attempts fail.
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class MovieWidget extends StatelessWidget { const MovieWidget({super.key}); @override Widget build(BuildContext context) { final movieProvider = Provider.of<MovieProvider>(context); return Scaffold( appBar: AppBar(title: const Text("Random Movie Recommender")), body: Center( child: movieProvider.isLoading ? const CircularProgressIndicator() // Show spinner while fetching : movieProvider.errorMessage != null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( movieProvider.errorMessage!, textAlign: TextAlign.center, style: const TextStyle(color: Colors.red), ), const SizedBox(height: 16), ElevatedButton( onPressed: () => movieProvider.fetchRandomMovie(), child: const Text("Try Again"), ), ], ) : movieProvider.currentMovie != null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( movieProvider.currentMovie!.title, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text("Release Date: ${movieProvider.currentMovie!.releaseDate}"), // Add other movie details (poster, overview, etc.) here ], ) : const Text("Tap the shuffle button to get started!"), ), floatingActionButton: FloatingActionButton( onPressed: () => movieProvider.fetchRandomMovie(), child: const Icon(Icons.shuffle), ), ); } }
- Max Retries: Don’t skip setting a max retry limit—this prevents your app from getting stuck in an infinite loop if the API is down or most IDs are invalid.
- API Rate Limits: The small delay between retries helps avoid hitting the API’s rate limits too quickly. Adjust the duration if needed based on the API’s rules.
- Validation Logic: Tweak the movie data check (
if (movie.id != null && movie.title.isNotEmpty)) to match your API’s actual response—some APIs might return a 404 status for invalid IDs, which will be caught in thecatchblock automatically.
内容的提问来源于stack exchange,提问作者user16304169

