Understanding How to Interact with GraphQL APIs in Flutter

GraphQL has emerged as a powerful alternative to traditional REST APIs, offering clients the ability to request specific data and avoid over-fetching. Flutter, with its growing popularity for building cross-platform applications, can efficiently interact with GraphQL APIs. This post delves into the methods of interacting with GraphQL APIs in Flutter, providing a comprehensive guide for developers.

What is GraphQL?

GraphQL is a query language for your API and a server-side runtime for executing those queries by using a type system you define for your data. Unlike REST, which exposes multiple endpoints, GraphQL exposes a single endpoint and allows clients to specify exactly what data they need.

Why Use GraphQL in Flutter?

  • Efficient Data Fetching: GraphQL retrieves only the data specified by the client, reducing data transfer and improving performance.
  • Strongly Typed: GraphQL schemas ensure type safety, making it easier to build and maintain client applications.
  • Real-time Capabilities: GraphQL supports subscriptions, enabling real-time updates from the server.

How to Interact with GraphQL APIs in Flutter

To interact with GraphQL APIs in Flutter, you’ll typically use a GraphQL client library. Several excellent options are available, but graphql_flutter is one of the most popular and well-maintained.

Step 1: Add Dependencies

First, add the graphql_flutter package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.0

Run flutter pub get to install the package.

Step 2: Configure GraphQL Client

Configure the GraphQL client to connect to your GraphQL API endpoint.

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

void main() {
  final HttpLink httpLink = HttpLink(
    'https://your-graphql-endpoint/graphql', // Replace with your GraphQL endpoint
  );

  final ValueNotifier client = ValueNotifier(
    GraphQLClient(
      link: httpLink,
      cache: GraphQLCache(store: InMemoryStore()),
    ),
  );

  var app = MaterialApp(
    home: GraphQLProvider(
      client: client,
      child: MyApp(),
    ),
  );

  runApp(app);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GraphQL Flutter App'),
      ),
      body: Center(
        child: Text('GraphQL is ready!'),
      ),
    );
  }
}

Explanation:

  • HttpLink: Creates a link to your GraphQL server.
  • GraphQLClient: The main client used to interact with your GraphQL server.
  • GraphQLProvider: Makes the GraphQLClient available to all widgets in your app.

Step 3: Making Queries

Use the Query widget to execute GraphQL queries and handle the results.

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

class GraphQLQueryExample extends StatelessWidget {
  final String getPostsQuery = """
    query GetPosts {
      posts {
        id
        title
        content
      }
    }
  """;

  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(getPostsQuery),
      ),
      builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
        if (result.hasException) {
          return Text(result.exception.toString());
        }

        if (result.isLoading) {
          return CircularProgressIndicator();
        }

        List? posts = result.data?['posts'];

        if (posts == null) {
          return Text('No posts');
        }

        return ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            final post = posts[index];
            return ListTile(
              title: Text(post['title']),
              subtitle: Text(post['content']),
            );
          },
        );
      },
    );
  }
}

Explanation:

  • getPostsQuery: A GraphQL query to fetch a list of posts with their id, title, and content.
  • Query Widget: Executes the GraphQL query and provides a builder function to handle the result.
  • Error Handling: Checks for errors via result.hasException and displays the error message.
  • Loading Indicator: Shows a CircularProgressIndicator while the data is being fetched.
  • Data Display: Renders the fetched posts in a ListView.

Step 4: Making Mutations

Use the Mutation widget to perform GraphQL mutations and update data on the server.

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

class GraphQLMutationExample extends StatefulWidget {
  @override
  _GraphQLMutationExampleState createState() => _GraphQLMutationExampleState();
}

class _GraphQLMutationExampleState extends State {
  final TextEditingController titleController = TextEditingController();
  final TextEditingController contentController = TextEditingController();

  final String createPostMutation = """
    mutation CreatePost(\$title: String!, \$content: String!) {
      createPost(title: \$title, content: \$content) {
        id
        title
        content
      }
    }
  """;

  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(createPostMutation),
        update: (cache, result) {
          // Optional: Update cache after mutation
        },
        onCompleted: (dynamic resultData) {
          // Optional: Handle completion
        },
      ),
      builder: (RunMutation runMutation, QueryResult result) {
        return Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              TextField(
                controller: titleController,
                decoration: InputDecoration(labelText: 'Title'),
              ),
              TextField(
                controller: contentController,
                decoration: InputDecoration(labelText: 'Content'),
              ),
              ElevatedButton(
                onPressed: () {
                  runMutation({
                    'title': titleController.text,
                    'content': contentController.text,
                  });
                },
                child: Text('Create Post'),
              ),
              if (result.isLoading) CircularProgressIndicator(),
              if (result.hasException) Text(result.exception.toString()),
            ],
          ),
        );
      },
    );
  }
}

Explanation:

  • createPostMutation: A GraphQL mutation to create a new post.
  • Mutation Widget: Provides a builder function with runMutation to execute the mutation.
  • Input Fields: Text fields for entering the title and content of the new post.
  • Executing Mutation: Calls runMutation with variables when the button is pressed.
  • Loading and Error Handling: Displays a loading indicator while the mutation is in progress and handles any errors.

Step 5: Subscriptions for Real-Time Updates

GraphQL subscriptions allow you to receive real-time updates from the server. To implement subscriptions, you’ll need a GraphQL server that supports them and a suitable client library (e.g., graphql_flutter with a WebSocket link).

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

class GraphQLSubscriptionExample extends StatefulWidget {
  @override
  _GraphQLSubscriptionExampleState createState() => _GraphQLSubscriptionExampleState();
}

class _GraphQLSubscriptionExampleState extends State {
  final String postAddedSubscription = """
    subscription {
      postAdded {
        id
        title
        content
      }
    }
  """;

  @override
  Widget build(BuildContext context) {
    final WebSocketLink websocketLink = WebSocketLink(
      'ws://your-graphql-endpoint/subscriptions', // Replace with your GraphQL subscription endpoint
      config: SocketClientConfig(
        autoReconnect: true,
        inactivityTimeout: Duration(seconds: 30),
      ),
    );

    return Subscription(
      options: SubscriptionOptions(
        document: gql(postAddedSubscription),
      ),
      builder: (BuildContext context, QueryResult? result) {
        if (result!.hasException) {
          return Text('Error: ${result.exception.toString()}');
        }

        if (result.isLoading) {
          return CircularProgressIndicator();
        }

        final post = result.data?['postAdded'];

        if (post == null) {
          return Text('No new posts');
        }

        return ListTile(
          title: Text('New Post: ${post['title']}'),
          subtitle: Text(post['content']),
        );
      },
    );
  }
}

Explanation:

  • WebSocketLink: Establishes a WebSocket connection to your GraphQL subscription endpoint.
  • Subscription Widget: Listens for real-time updates and provides a builder function to handle the results.
  • Data Handling: Displays the new post data when a new post is added.

Conclusion

Interacting with GraphQL APIs in Flutter provides an efficient and type-safe way to fetch and update data. Using the graphql_flutter package, you can easily perform queries, mutations, and subscriptions, enabling you to build high-performance and real-time Flutter applications. By leveraging GraphQL’s strengths, you can optimize data fetching, reduce network overhead, and create a better user experience. This guide provides a foundation for integrating GraphQL into your Flutter projects, helping you take advantage of modern API technologies.