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

首次使用GraphQL,如何在Flutter中集成提供的新闻API?

Flutter 对接 GraphQL 新闻接口实操指南

第一次接触GraphQL不用先啃完所有原理,先把接口跑通再慢慢补概念就行。你拿到的是Hasura引擎提供的GraphQL接口,鉴权靠请求头里的密钥,给定的查询语句就是要拉取新闻表的9个字段,按下面步骤走就能完成对接:

1. 添加项目依赖

打开项目根目录的pubspec.yaml,在dependencies下加入稳定版GraphQL客户端包,执行flutter pub get安装:

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.2

2. 初始化GraphQL客户端

和普通HTTP请求要配baseUrl、请求头逻辑一致,GraphQL客户端需要先配置接口地址和鉴权信息,注意把代码里的接口地址、密钥替换成给你的真实值:

import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

void main() async {
  // 初始化本地缓存,不写这行会启动报错
  await initHiveForFlutter();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // 配置请求链接和鉴权头
    final HttpLink httpLink = HttpLink(
      '替换成你拿到的Hasura接口完整地址,一般后缀为/v1/graphql',
      defaultHeaders: {
        "x-hasura-admin-secret": "替换成给你的真实secret值"
      },
    );

    // 初始化客户端
    final ValueNotifier<GraphQLClient> client = ValueNotifier(
      GraphQLClient(
        link: httpLink,
        cache: GraphQLCache(store: HiveStore()),
      ),
    );

    // 把客户端注入到整个应用上下文
    return GraphQLProvider(
      client: client,
      child: MaterialApp(
        title: '新闻应用',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: const NewsListPage(),
      ),
    );
  }
}

3. 编写查询逻辑渲染新闻列表

把给你的查询语句定义成字符串常量,用库提供的Query组件直接处理加载、报错、数据渲染三种状态,不用自己手写底层请求逻辑:

// 定义查询语句,和给你的内容完全一致就行,别改字段大小写
const String fetchNewsQuery = """
query getNews {
  news {
    title
    url
    publishedAt
    author
    content
    description
    h_id
    source
    urlToImage
  }
}
""";

class NewsListPage extends StatelessWidget {
  const NewsListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('新闻列表')),
      body: Query(
        options: QueryOptions(document: gql(fetchNewsQuery)),
        builder: (QueryResult result, {VoidCallback? refetch, FetchMore? fetchMore}) {
          // 加载中状态
          if (result.isLoading) {
            return const Center(child: CircularProgressIndicator());
          }
          // 请求报错状态
          if (result.hasException) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 24),
                    child: Text('加载失败:${result.exception.toString()}'),
                  ),
                  const SizedBox(height: 12),
                  ElevatedButton(
                    onPressed: refetch,
                    child: const Text('重试'),
                  )
                ],
              ),
            );
          }
          // 解析返回数据
          final List newsData = result.data?['news'] ?? [];
          if (newsData.isEmpty) {
            return const Center(child: Text('暂无新闻内容'));
          }
          // 渲染列表
          return RefreshIndicator(
            onRefresh: () async => refetch?.call(),
            child: ListView.builder(
              padding: const EdgeInsets.all(8),
              itemCount: newsData.length,
              itemBuilder: (context, index) {
                final singleNews = newsData[index];
                return Card(
                  margin: const EdgeInsets.only(bottom: 8),
                  child: ListTile(
                    contentPadding: const EdgeInsets.all(12),
                    leading: singleNews['urlToImage'] != null
                        ? Image.network(
                            singleNews['urlToImage'],
                            width: 90,
                            fit: BoxFit.cover,
                            errorBuilder: (_, __, ___) => const SizedBox(width: 90, child: Icon(Icons.broken_image)),
                          )
                        : const SizedBox(width: 90, child: Icon(Icons.newspaper)),
                    title: Text(
                      singleNews['title'] ?? '无标题',
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontWeight: FontWeight.w500),
                    ),
                    subtitle: Padding(
                      padding: const EdgeInsets.only(top: 8),
                      child: Text(
                        '${singleNews['author'] ?? '未知作者'} · ${singleNews['publishedAt'] ?? ''}',
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                    onTap: () {
                      // 这里加跳新闻详情、打开原链接的逻辑即可
                    },
                  ),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

实际开发注意点

  • 不要把x-hasura-admin-secret硬编码在客户端代码里,这个密钥拥有整个数据库的操作权限,正式上线必须换成后端签发的JWT做用户级鉴权,不然客户端被反编译后数据会被篡改
  • Hasura的GraphQL接口固定后缀是/v1/graphql,不要填成控制台的网页地址
  • GraphQL对字段大小写敏感,查询语句里的字段名要和给你的完全一致,写错会直接报字段不存在的错误
  • 调试时如果报跨域错误,找后端配置Hasura的CORS规则,允许你本地调试IP和正式上线的域名即可
  • 要做下拉刷新直接调用refetch方法就行,不用重新初始化请求

内容的提问来源于stack exchange,提问作者Chirag Maurya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 22:57:32