Using the graphql_flutter Package to Interact with GraphQL Endpoints in Flutter

Flutter, Google’s UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, is becoming increasingly popular for its efficiency and beautiful user interfaces. When it comes to interacting with data, GraphQL has emerged as a powerful alternative to traditional REST APIs, offering more flexibility and efficiency in data fetching. In Flutter, the graphql_flutter package provides a robust and streamlined way to interact with GraphQL endpoints. This article delves into how to effectively use the graphql_flutter package to perform various GraphQL operations in your Flutter application.

What is GraphQL?

GraphQL is a query language for your API and a server-side runtime for executing those queries. Unlike REST, which exposes multiple endpoints and often leads to over-fetching or under-fetching of data, GraphQL allows clients to request exactly the data they need and nothing more. This precision reduces network overhead and improves app performance.

Why Use graphql_flutter?

  • Simplified Integration: Provides a clean and intuitive API for interacting with GraphQL servers.
  • Caching: Offers built-in caching mechanisms to reduce network requests and improve performance.
  • Automatic Code Generation: Supports generating type-safe models from GraphQL schemas to avoid manual parsing and increase code safety.
  • Subscription Support: Enables real-time updates using GraphQL subscriptions for building reactive applications.

Setting Up Your Flutter Project

Step 1: Add the Dependency

To start using graphql_flutter, you need to add it to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.0 # Use the latest version

Then, run flutter pub get in your terminal to install the package.

Step 2: Configure GraphQL Client

Next, configure the GraphQL client with the URL of your GraphQL endpoint and optionally, an HTTP link with authorization headers if required. Wrap your app with the GraphQLProvider widget.

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 URL
  );

  // Optionally add authentication headers
  final AuthLink authLink = AuthLink(
    getToken: () async => 'Bearer YOUR_PERSONAL_ACCESS_TOKEN', // Replace with your token
  );

  final Link link = authLink.concat(httpLink);

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

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

  runApp(app);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter GraphQL Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'GraphQL Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Text('GraphQL Flutter Demo'),
      ),
    );
  }
}

This setup ensures that the GraphQL client is accessible throughout your application via the GraphQLProvider.

Performing Queries

Fetching data is a common task. The graphql_flutter package provides the Query widget to perform GraphQL queries.

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

class GraphQLQueryDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final String readRepositories = """
      query ReadRepositories($nRepositories: Int!) {
        viewer {
          repositories(last: $nRepositories) {
            nodes {
              id
              name
              description
              url
            }
          }
        }
      }
    """;

    return Query(
      options: QueryOptions(
        document: gql(readRepositories),
        variables: {
          'nRepositories': 10,
        },
      ),
      builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
        if (result.hasException) {
          return Text(result.exception.toString());
        }

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

        List? repositories = result.data?['viewer']['repositories']['nodes'];

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

        return ListView.builder(
          itemCount: repositories.length,
          itemBuilder: (context, index) {
            final repository = repositories[index];
            return ListTile(
              title: Text(repository['name'] ?? 'No Name'),
              subtitle: Text(repository['description'] ?? 'No Description'),
              onTap: () {
                // Handle repository tap
              },
            );
          },
        );
      },
    );
  }
}

In this example:

  • A GraphQL query is defined to fetch the last 10 repositories of the viewer.
  • The Query widget executes the query and rebuilds the UI based on the query result.
  • Error, loading, and success states are handled to provide appropriate feedback to the user.

Executing Mutations

To modify data, use the Mutation widget. It allows you to execute GraphQL mutations, such as creating or updating records.

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

class GraphQLMutationDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final String addStar = """
      mutation AddStar($starrableId: ID!) {
        addStar(input: {starrableId: $starrableId}) {
          starrable {
            id
            viewerHasStarred
          }
        }
      }
    """;

    return Mutation(
      options: MutationOptions(
        document: gql(addStar),
        onCompleted: (dynamic result) {
          print('Mutation completed: $result');
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
            content: Text('Starred!'),
          ));
        },
      ),
      builder: (RunMutation runMutation, QueryResult? result) {
        return ElevatedButton(
          onPressed: () {
            runMutation({
              'starrableId': 'MDEwOlJlcG9zaXRvcnkxMjM0NTY3ODk=', // Replace with an actual starrable ID
            });
          },
          child: Text('Star Repository'),
        );
      },
    );
  }
}

Key points:

  • The Mutation widget requires a GraphQL mutation string.
  • The runMutation function is called when the button is pressed, triggering the mutation.
  • The onCompleted callback handles the result after the mutation is successfully executed.

Using Subscriptions for Real-Time Updates

GraphQL subscriptions allow clients to receive real-time updates from the server. This is useful for building features like live comments or real-time notifications.

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

class GraphQLSubscriptionDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final String subscribeToComments = """
      subscription SubscribeToComments($repoFullName: String!) {
        github(repoFullName: $repoFullName) {
          commentAdded {
            id
            content
          }
        }
      }
    """;

    return Subscription(
      options: SubscriptionOptions(
        document: gql(subscribeToComments),
        variables: {
          'repoFullName': 'flutter/flutter', // Example repo
        },
      ),
      builder: (BuildContext context,
          SubscriptionResult? result) {
        if (result!.hasException) {
          return Text("Exception: ${result.exception}");
        }

        if (result.isLoading) {
          return Text('Loading...');
        }

        final comment = result.data?['github']['commentAdded'];

        if (comment != null) {
          return Text('New Comment: ${comment['content']}');
        }

        return Text('No new comments');
      },
    );
  }
}

Explanation:

  • The Subscription widget sets up a real-time connection to the GraphQL server.
  • The builder function updates the UI whenever a new comment is received.
  • Loading and error states are handled to provide a smooth user experience.

Caching

graphql_flutter supports caching query results, reducing the need to make repeated requests for the same data. By default, it uses an in-memory cache. For persistent caching, you can use HiveStore.

final client = ValueNotifier(
  GraphQLClient(
    cache: GraphQLCache(store: HiveStore()), // Using Hive for persistent caching
    link: httpLink,
  ),
);

Persistent caching improves offline capabilities and reduces data usage, making your app more efficient.

Automatic Code Generation

To avoid manually parsing GraphQL responses and ensure type safety, consider using code generation tools like graphql_codegen or artemis. These tools generate Dart models from your GraphQL schema, which you can use directly in your application.

Conclusion

The graphql_flutter package offers a comprehensive and efficient way to interact with GraphQL endpoints in your Flutter applications. With support for queries, mutations, subscriptions, and caching, it streamlines data fetching and manipulation while providing a great developer experience. By integrating GraphQL into your Flutter projects, you can build high-performance, data-driven applications that meet the demands of modern users.