You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:使用Model类从TMDB API取数据后ListView无法显示问题

问题分析与修复方案

你的核心问题是Future抛出异常但未被处理,加上FutureBuilder没有判断错误状态,导致snapshot.data始终为null,ListView无法渲染。以下是具体问题点和修复步骤:

核心问题

  1. 未捕获异步异常:getTopRatedMovies方法中,网络请求或数据解析出错时没有捕获异常,导致Future进入错误状态,但FutureBuilder只判断了hasData,忽略了hasError,因此一直显示加载圈。
  2. 重复创建Future:在build方法中直接调用getTopRatedMovies(),会导致每次组件重建都重新发起请求,引发异步逻辑混乱。
  3. 空值未处理:电影数据的可空字段(如posterPath、title)没有做空值判断,可能触发空指针异常。

修复后的代码(movielist_listview.dart)

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:movipedia/model/top_rated_movies/results.dart';

class TopRatedMoviesCircularListView extends StatefulWidget {
  const TopRatedMoviesCircularListView({Key? key}) : super(key: key);

  @override
  State<TopRatedMoviesCircularListView> createState() => _TopRatedMoviesCircularListViewState();
}

class _TopRatedMoviesCircularListViewState extends State<TopRatedMoviesCircularListView> {
  late Future<List<Results>> _moviesFuture;

  @override
  void initState() {
    super.initState();
    // 初始化Future,避免build重复触发请求
    _moviesFuture = getTopRatedMovies();
  }

  Future<List<Results>> getTopRatedMovies() async {
    try {
      var url =
          "https://api.themoviedb.org/3/discover/movie?api_key=dc92a7524c83f242a3237b2222941f00&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=28";
      var response = await Dio().get(url);
      
      if (response.statusCode != 200) {
        throw Exception('请求失败:状态码${response.statusCode}');
      }

      // 添加类型断言,避免动态类型隐患
      var topRatedMovies = response.data as Map<String, dynamic>;
      final List<Results> results = (topRatedMovies['results'] as List)
          .map<Results>((json) => Results.fromJson(json as Map<String, dynamic>))
          .toList();
      return results;
    } catch (e) {
      print('数据加载错误:$e');
      throw e; // 抛出错误让FutureBuilder捕获
    }
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<Results>>(
      future: _moviesFuture,
      builder: (context, snapshot) {
        // 打印错误信息方便排查
        print('snapshot error: ${snapshot.error}');
        
        if (snapshot.hasData) {
          return ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: snapshot.data!.length,
            itemBuilder: (BuildContext context, int index) {
              final movie = snapshot.data![index];
              return Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  children: [
                    // 处理海报为空或加载失败的情况
                    movie.posterPath != null
                        ? Image.network(
                            'https://image.tmdb.org/t/p/w500${movie.posterPath}',
                            width: 100,
                            height: 150,
                            fit: BoxFit.cover,
                            errorBuilder: (context, error, stackTrace) {
                              return const Icon(Icons.broken_image, color: Colors.white, size: 100);
                            },
                          )
                        : const Icon(Icons.image_not_supported, color: Colors.white, size: 100),
                    // 处理空标题
                    Text(
                      movie.title ?? '无标题',
                      style: const TextStyle(color: Colors.white, fontSize: 16),
                      textAlign: TextAlign.center,
                    ),
                    Text(
                      'Average Rating: ${movie.voteAverage ?? 0.0}',
                      style: const TextStyle(color: Colors.white, fontSize: 12),
                    ),
                    Text(
                      'Release Date: ${movie.releaseDate ?? '未知'}',
                      style: const TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  ],
                ),
              );
            },
          );
        } else if (snapshot.hasError) {
          // 显示错误信息
          return Center(
            child: Text('加载失败:${snapshot.error}', style: const TextStyle(color: Colors.white)),
          );
        } else {
          // 加载中状态
          return const Center(child: CircularProgressIndicator());
        }
      },
    );
  }
}

关键修改点说明

  1. 改为StatefulWidget:在initState中初始化Future,避免build重复发起请求。
  2. 异常捕获:用try-catch包裹请求和解析逻辑,打印错误并抛出,让FutureBuilder能捕获到错误状态。
  3. 错误状态处理:在FutureBuilder中添加hasError判断,显示错误信息,方便排查问题。
  4. 类型安全与空值处理:添加类型断言避免动态类型隐患,对可空字段做空值判断,防止空指针异常。
  5. 图片加载容错:给Image.network添加errorBuilder,处理海报加载失败的情况。

内容的提问来源于stack exchange,提问作者kadir yapar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 23:24:55