Flutter获取新闻API数据成功但页面无法显示问题求助
Flutter新闻应用空白页面问题修复
问题描述
开发一款从免费API获取新闻并按分类展示的Flutter应用,已能在控制台打印新闻数据,但模拟器屏幕显示空白页面,无报错。仅在多次重启或重复点击分类后偶尔能正常展示,概率极低。
问题代码
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return MaterialApp( title: 'Book App', debugShowCheckedModeBanner: false, home: CategoryScreen(), routes: { 'news screen': (ctx) => NewsScreen(), }); } } class NewsScreen extends StatefulWidget { @override State<NewsScreen> createState() => _NewsScreenState(); } class _NewsScreenState extends State<NewsScreen> { List<News> news = []; Future<void> fetchNews(String title) async { final url = Uri.parse( 'https://newsapi.org/v2/top-headlines?country=us&category=$title&apiKey=89b5c4927784426688b6df37e75ba989'); try { final response = await http.get(url); final extractedData = json.decode(response.body) as Map<String, dynamic>; final List<News> products = []; print(extractedData['articles'].length); for (var i = 0; i < extractedData['articles'].length; i++) { print("here"); products.add(News( title: extractedData['articles'][i]['title'], imageUrl: extractedData['articles'][i]['url'], description: extractedData['articles'][i]['description'], )); } news = products; } catch (error) { throw (error); } } @override Widget build(BuildContext context) { final routeArgs = ModalRoute.of(context)!.settings.arguments as Map<String, String>; final String categoryTitle = routeArgs['title']!; fetchNews(categoryTitle); final categoryBooks = news.where((book) { return book.title.contains(categoryTitle); }).toList(); return Scaffold( appBar: AppBar( centerTitle: true, title: Text(categoryTitle), ), body: Center( child: ListView.builder( itemCount: news.length, itemBuilder: (ctx, index) { return Card( child: Container( decoration: BoxDecoration( color: Colors.brown[100], borderRadius: BorderRadius.circular(10.0), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: Offset(0, 3), ), ], ), margin: EdgeInsets.all(10.0), child: Column( children: [ Text( news[0].title, style: TextStyle( fontWeight: FontWeight.bold, color: Colors.brown, ), ), Text( news[0].description, style: TextStyle( fontWeight: FontWeight.bold, color: Colors.brown, ), ), ], ), ), ); }, ), ), ); } } class CategoryScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Categories'), ), body: GridView( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, childAspectRatio: 3 / 2, crossAxisSpacing: 20, mainAxisSpacing: 20, ), children: categories .map((bookData) => CategoryItem(bookData.id, bookData.title, bookData.color)) .toList(), ), ); } } const categories = [ Category(id: "1", title: "business", color: Colors.pink), Category(id: "2", title: "entertainment", color: Colors.blue), Category(id: "3", title: "general", color: Colors.red), Category(id: "4", title: "health", color: Colors.green), Category(id: "5", title: "science", color: Colors.purple), Category(id: "6", title: "sports", color: Colors.orange), Category(id: "7", title: "technology", color: Colors.brown), ]; class News { late final String title; late final String imageUrl; late final String description; News( {required this.title, required this.imageUrl, required this.description}); News.fromJson(Map<String, dynamic> json) { title = json['title']; imageUrl = json['url']; description = json['description']; } } class Category { final String id; final String title; final Color color; const Category( {required this.id, required this.title, this.color = Colors.orange}); } class CategoryItem extends StatefulWidget { final String id; final String title; final Color color; CategoryItem(this.id, this.title, this.color); @override State<CategoryItem> createState() => _CategoryItemState(); } class _CategoryItemState extends State<CategoryItem> { void selectCategory(BuildContext ctx) { Navigator.of(ctx).pushNamed( 'news screen', arguments: { 'id': widget.id, 'title': widget.title, }, ); } @override Widget build(BuildContext context) { return InkWell( onTap: () => selectCategory(context), borderRadius: BorderRadius.circular(10), child: Container( alignment: Alignment.center, child: Text( widget.title, style: TextStyle( fontSize: 22, color: Colors.white, ), ), decoration: BoxDecoration( gradient: LinearGradient( colors: [ widget.color.withOpacity(0.7), widget.color, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(15), ), ), ); } }
问题根源
- 异步请求未触发UI更新:在
build方法中直接调用fetchNews,网络请求完成后未调用setState通知Flutter刷新UI,初始渲染时news为空列表,页面空白;偶尔显示是因为状态被意外刷新(如热重载、页面重绘)。 - 列表项固定取第一条数据:
ListView.builder的itemBuilder中始终使用news[0],导致所有列表项显示同一条新闻,且当news为空时会崩溃。 - 冗余代码:
categoryBooks变量未被使用,属于无效代码。
修复方案
1. 正确处理异步请求与UI更新
将fetchNews移到initState中执行,并在请求完成后调用setState更新状态:
@override void initState() { super.initState(); // 等待页面渲染完成后获取路由参数 WidgetsBinding.instance.addPostFrameCallback((_) { final routeArgs = ModalRoute.of(context)!.settings.arguments as Map<String, String>; final String categoryTitle = routeArgs['title']!; fetchNews(categoryTitle); }); } Future<void> fetchNews(String title) async { final url = Uri.parse( 'https://newsapi.org/v2/top-headlines?country=us&category=$title&apiKey=89b5c4927784426688b6df37e75ba989'); try { final response = await http.get(url); final extractedData = json.decode(response.body) as Map<String, dynamic>; final List<News> products = []; // 遍历articles时处理null值,避免崩溃 for (var article in extractedData['articles']) { if (article['title'] != null && article['description'] != null) { products.add(News( title: article['title'], imageUrl: article['url'] ?? '', description: article['description'], )); } } // 更新状态,触发UI刷新 setState(() { news = products; }); } catch (error) { print('获取新闻失败: $error'); // 可添加错误提示逻辑,如弹出SnackBar } }
2. 修复列表项数据获取逻辑
在itemBuilder中使用index获取对应位置的新闻:
itemBuilder: (ctx, index) { final currentNews = news[index]; return Card( child: Container( decoration: BoxDecoration( color: Colors.brown[100], borderRadius: BorderRadius.circular(10.0), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: const Offset(0, 3), ), ], ), margin: const EdgeInsets.all(10.0), child: Column( children: [ Text( currentNews.title, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.brown, ), ), Text( currentNews.description, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.brown, ), ), ], ), ), ); },
3. 删除冗余代码并添加加载状态
移除build方法中的fetchNews(categoryTitle)和categoryBooks变量,同时添加加载状态提示:
@override Widget build(BuildContext context) { final routeArgs = ModalRoute.of(context)!.settings.arguments as Map<String, String>; final String categoryTitle = routeArgs['title']!; return Scaffold( appBar: AppBar( centerTitle: true, title: Text(categoryTitle), ), body: Center( child: news.isEmpty ? const CircularProgressIndicator() : ListView.builder( itemCount: news.length, itemBuilder: (ctx, index) { final currentNews = news[index]; return Card( child: Container( decoration: BoxDecoration( color: Colors.brown[100], borderRadius: BorderRadius.circular(10.0), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: const Offset(0, 3), ), ], ), margin: const EdgeInsets.all(10.0), child: Column( children: [ Text( currentNews.title, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.brown, ), ), Text( currentNews.description, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.brown, ), ), ], ), ), ); }, ), ), ); }
内容的提问来源于stack exchange,提问作者arshmed
相关产品推荐
相关产品推荐

