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

Flutter中如何通过FutureBuilder展示JSON接口获取的股票新闻数据

Flutter 股票App新闻列表渲染实现方案

先修正现有接口请求方法的问题

你现有的getNews方法存在两个需要调整的点:

  1. 没有明确返回值类型,且对应API返回的新闻列表实际存放在data字段中,直接解析根JSON会触发报错
  2. 调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 16:36:03