Flutter中如何通过FutureBuilder展示JSON接口获取的股票新闻数据
Flutter 股票App新闻列表渲染实现方案
先修正现有接口请求方法的问题
你现有的getNews方法存在两个需要调整的点:
- 没有明确返回值类型,且对应API返回的新闻列表实际存放在
data字段中,直接解析根JSON会触发报错 - 调用map转换后需要转为List类型,否则无法直接在列表组件中使用
修正后的代码如下:
// 明确返回类型为Future<List<News>> Future<List<News>> getNews() async { final response = await http.get(Uri.parse( 'https://api.stockdata.org/v1/news/all?&filter_entities=true&language=en&api_token=${api_token}&countries=${country}')); if (response.statusCode == 200) { final jsonResponse = json.decode(response.body); // 从返回结果中取data字段的数组进行转换 List newsList = jsonResponse['data']; return newsList.map((data) => News.fromJSON(data)).toList(); } else { throw Exception('Unexpected error occurred!'); } }
FutureBuilder 运行逻辑&使用示例
FutureBuilder是Flutter官方封装的异步请求UI渲染组件,核心逻辑很简单:
- 传入你要执行的异步任务(这里就是
getNews()方法返回的Future) - 它会自动监听异步任务的状态,你只要根据不同状态返回对应UI即可:
- 异步任务执行中:返回加载提示组件
- 异步任务执行失败:返回错误提示组件
- 异步任务执行成功:拿到返回的新闻列表渲染列表UI
完整的新闻列表页面实现代码如下:
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; // 你之前定义的News类 class News { final String title; final String desc; final String imgURL; final String url; News({ required this.title, required this.desc, required this.imgURL, required this.url }); factory News.fromJSON(Map<String, dynamic> json) { final title = json["title"] as String; final desc = json["description"] as String; final imgUrl = json["image_url"] as String; final url = json["url"] as String; return News(title: title, desc: desc, imgURL: imgUrl, url: url); } } class NewsListPage extends StatelessWidget { // 把api_token和country替换成你自己的实际参数 final String api_token = "你的API令牌"; final String country = "us"; Future<List<News>> getNews() async { final response = await http.get(Uri.parse( 'https://api.stockdata.org/v1/news/all?&filter_entities=true&language=en&api_token=${api_token}&countries=${country}')); if (response.statusCode == 200) { final jsonResponse = json.decode(response.body); List newsList = jsonResponse['data']; return newsList.map((data) => News.fromJSON(data)).toList(); } else { throw Exception('Unexpected error occurred!'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("股票新闻")), body: FutureBuilder<List<News>>( // 传入异步请求方法 future: getNews(), builder: (context, snapshot) { // 异步请求出错的情况 if (snapshot.hasError) { return Center( child: Text("加载失败:${snapshot.error}"), ); } // 异步请求成功拿到数据的情况 if (snapshot.hasData) { List<News> news = snapshot.data!; // 用ListView渲染新闻列表 return ListView.builder( padding: const EdgeInsets.all(12), itemCount: news.length, itemBuilder: (context, index) { final item = news[index]; return Card( margin: const EdgeInsets.only(bottom: 12), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 新闻封面图 if (item.imgURL.isNotEmpty) Image.network( item.imgURL, height: 180, width: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return const SizedBox( height: 180, child: Icon(Icons.broken_image, size: 48), ); }, ), const SizedBox(height: 8), // 新闻标题 Text( item.title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold ), ), const SizedBox(height: 4), // 新闻摘要 Text( item.desc, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.grey), ) ], ), ), ); }, ); } // 默认是请求中的状态,显示加载动画 return const Center( child: CircularProgressIndicator(), ); }, ), ); } }
如果需要点击新闻跳转到详情页,可以给Card加GestureDetector,点击后通过WebView加载item.url对应的网页即可。
内容的提问来源于stack exchange,提问作者Abhi
相关产品推荐
相关产品推荐

