How to send post request to graphql API in flutter

3,083

This took me a minute to figure out, too, but here is what I did in my practice todo app:

1 - Read this page on graphql post requests over http. There is a section for GET Requests as well as POST.

2 - Make sure your body function argument is correctly json-encoded (see code below).

Tip: Using Postman, you can test the graphql endpoint w/different headers & authorization tokens, and request bodies. It also has a neat feature to generate code from the request. Check out this page for details. It's not 100% accurate, but that's what helped me figure out how to properly format the request body. In the function post, apparently you can't change the content-type if you provide a Map as the body of the request (and the request content types is application/json), so a String worked for my use case.

Sample Code (uses a GqlParser class to properly encode the request 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

Per the .post() body argument documentation:

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.

I simplified the creation of a string to provide as the body of an argument with the following code below, in a GqlParser class. This will allow you to have a folder such as graphql that contains multiple *.graphql queries/mutations. Then you simply use the parser in your other classes that need to make simple graphql endpoint requests, and provide the name of the file (without the extension).

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);
  }
}
Share:
3,083
Joppe De Jonghe
Author by

Joppe De Jonghe

Updated on December 24, 2022

Comments

  • Joppe De Jonghe
    Joppe De Jonghe over 1 year

    I'm trying to learn how to use rails combined with graphql to create a rails API by developing a simple app that just retrieves text (in my case, quotes) from a database and shows it on screen. I am using flutter for frontend and rails with graphql as the backend. The backend part was easy to create because I already had some rails knowledge but the frontend part is something I'm new to and I'm trying to figure out how to access a graphql query that I created via flutter to get the data that needs to be displayed.

    Below is the flutter code that I currently have (partially adapted from How to build a mobile app from scratch with Flutter and maybe Rails?).

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

    Some obvious reasons why this code is wrong that I already figured out myself is that the graphql server expects a post request for the query while my code is sending a get request but that is my question. How do I send a post request for my graphql server in flutter to retrieve the data? The query that I'm trying to access is the one after '?query=' in my flutter code.

    • Kris Leland
      Kris Leland over 3 years
      Easiest thing first, check out flutter.dev/docs/cookbook/networking/send-data for how to http.post. Less easy, your server seems to be configured incorrectly. Get is used when fetching data, Post when changing something. It looks like you're correctly trying to Get in the code that I can see so my guess is the server is wrong.