如何在 flutter 中向 graphql API 发送 post 请求

How to send post request to graphql API in flutter

我正在尝试学习如何使用 rails 结合 graphql 来创建 rails API 通过开发一个简单的应用程序来检索文本(在我的例子中,引号) 从数据库中提取并显示在屏幕上。我在前端使用 flutter,在后端使用 graphql rails。后端部分很容易创建,因为我已经有了一些 rails 知识,但前端部分是我的新手,我正在尝试弄清楚如何访问我通过 flutter 创建的 graphql 查询以获取需要显示的数据。

下面是我目前的flutter代码(部分改编自

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<Quote> fetchQuote() async {
  final response =
      await http.get('http://10.0.2.2:3000/graphql?query={quote{text}}');

  if (response.statusCode == 200) {
    // If the call to the server was successful, parse the JSON.
    return Quote.fromJson(json.decode(response.body));
  } else {
    // If that call was not successful, throw an error.
    throw Exception('Failed to load quote');
  }
}

class Quote {
  final String text;

  Quote({this.text});

  factory Quote.fromJson(Map<String, dynamic> json) {
    return Quote(
      text: json['text']
    );
  }
}


void main() => runApp(MyApp(quote: fetchQuote()));

class MyApp extends StatelessWidget {
  final Future<Quote> quote;

  MyApp({this.quote});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Fetch Data Example'),
        ),
        body: Center(
          child: FutureBuilder<Quote>(
            future: quote,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data.text);
              } else if (snapshot.hasError) {
                return Text("${snapshot.error}");
              }

              // By default, show a loading spinner.
              return CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

我自己已经弄明白为什么这段代码错误的一些明显原因是 graphql 服务器需要一个 post 查询请求,而我的代码正在发送一个 get 请求,但这是我的问题。 如何在 flutter 中向我的 graphql 服务器发送 post 请求以检索数据? 我尝试访问的查询是“?query=”之后的查询在我的 flutter 代码中。

这也花了我一分钟的时间才弄清楚,但这是我在练习待办事项应用程序中所做的:

1 - 阅读 this page on graphql post requests over http。有一个部分用于 GET 请求以及 POST.

2 - 确保你的 body 函数参数正确 json-encoded(见下面的代码)。

提示:使用 Postman,您可以测试 graphql 端点 w/different headers & 授权令牌和请求主体。它还有一个简洁的功能,可以根据请求生成代码。查看 this page for details。它不是 100% 准确,但这帮助我弄清楚如何正确格式化请求 body。在函数 post 中,如果您提供 Map 作为请求的 body(并且请求内容类型为 application/json),显然您无法更改 content-type,所以一个字符串适用于我的用例。

示例代码(使用 GqlParser class 正确编码请求 body):

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'todo.dart';
import '../creds/creds.dart';
import 'gql_parser.dart';

const parser = GqlParser('bin/graphql');

class TodoApiException implements Exception {
  const TodoApiException(this.message);
  final String message;
}

class TodoApiClient {
  const TodoApiClient();
  static final gqlUrl = Uri.parse(Credential.gqlEndpoint);
  static final headers = {
    "x-hasura-admin-secret": Credential.gqlAdminSecret,
    "Content-Type": "application/json",
  };

  Future<List<Todo>> getTodoList(int userId) async {
    final response = await http.post(
      gqlUrl,
      headers: headers,
      body: parser.gqlRequestBody('users_todos', {'userId': userId}),
    );

    if (response.statusCode != 200) {
      throw TodoApiException('Error fetching todos for User ID $userId');
    }

    final decodedJson = jsonDecode(response.body)['data']['todos'] as List;
    var todos = <Todo>[];

    decodedJson.forEach((todo) => todos.add(Todo.fromJson(todo)));
    return todos;
  }
// ... rest of class code ommitted

根据 .post() body 参数文档:

If it's a String, it's encoded using [encoding] and used as the body of the request. The content-type of the request will default to "text/plain".

If [body] is a List, it's used as a list of bytes for the body of the request.

If [body] is a Map, it's encoded as form fields using [encoding]. The content-type of the request will be set to "application/x-www-form-urlencoded"; this cannot be overridden.

我简化了字符串的创建,以在 GqlParser class 中使用下面的代码作为参数的 body 提供。这将允许您拥有一个包含多个 *.graphql queries/mutations 的文件夹,例如 graphql。然后,您只需在需要发出简单 graphql 端点请求的其他 class 中使用 parser,并提供文件名(不带扩展名)。

import 'dart:convert';
import 'dart:io';

class GqlParser {
  /// provide the path relative to of the folder containing graphql queries, with no trailing or leading "/".
  /// For example, if entire project is inside the `my_app` folder, and graphql queries are inside `bin/graphql`,
  /// use `bin/graphql` as the argument.
  const GqlParser(this.gqlFolderPath);

  final String gqlFolderPath;

  /// Provided the name of the file w/out extension, will return a string of the file contents
  String gqlToString(String fileName) {
    final pathToFile =
        '${Directory.current.path}/${gqlFolderPath}/${fileName}.graphql';
    final gqlFileText = File(pathToFile).readAsLinesSync().join();
    return gqlFileText;
  }

  /// Return a json-encoded string of the request body for a graphql request, given the filename (without extension)
  String gqlRequestBody(String gqlFileName, Map<String, dynamic> variables) {
    final body = {
      "query": this.gqlToString(gqlFileName),
      "variables": variables
    };
    return jsonEncode(body);
  }
}