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

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),
        ),
      ),
    );
  }
}

问题根源

  1. 异步请求未触发UI更新:在build方法中直接调用fetchNews,网络请求完成后未调用setState通知Flutter刷新UI,初始渲染时news为空列表,页面空白;偶尔显示是因为状态被意外刷新(如热重载、页面重绘)。
  2. 列表项固定取第一条数据:ListView.builder的itemBuilder中始终使用news[0],导致所有列表项显示同一条新闻,且当news为空时会崩溃。
  3. 冗余代码: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 12:05:54