Writing GraphQL Queries and Mutations to Fetch and Modify Data in Flutter

GraphQL has emerged as a powerful alternative to traditional REST APIs, offering more flexibility and efficiency in data fetching and manipulation. Flutter developers can leverage GraphQL to build performant and data-efficient mobile applications. This article explores how to write GraphQL queries and mutations in Flutter to fetch and modify data seamlessly.

What is GraphQL?

GraphQL is a query language for your API and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.

Why Use GraphQL in Flutter?

  • Efficient Data Fetching: GraphQL allows you to request specific data fields, reducing over-fetching and under-fetching.
  • Strongly Typed Schema: The GraphQL schema ensures type safety and improves developer understanding of the API.
  • Single Endpoint: Unlike REST, GraphQL uses a single endpoint for all queries, simplifying API management.

Setting Up GraphQL in Flutter

To use GraphQL in your Flutter project, you’ll need to set up the necessary dependencies and configure a GraphQL client.

Step 1: Add Dependencies

Add the graphql_flutter package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.0

Then, run flutter pub get to install the package.

Step 2: Configure GraphQL Client

Create a GraphQL client using GraphQLClient. You’ll need to specify the GraphQL endpoint and configure the cache:

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

void main() {
  final HttpLink httpLink = HttpLink(
    'https://your-graphql-endpoint.com/graphql',
  );

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

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

  runApp(app);
}

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

Replace 'https://your-graphql-endpoint.com/graphql' with your actual GraphQL endpoint.

Writing GraphQL Queries

Queries are used to fetch data from the GraphQL server. Here’s how to write and execute GraphQL queries in Flutter.

Step 1: Define the Query

Create a GraphQL query using a String. This query specifies the data you want to retrieve:

const String readRepositories = """
  query ReadRepositories($nRepositories: Int!) {
    viewer {
      repositories(last: $nRepositories) {
        nodes {
          id
          name
          url
        }
      }
    }
  }
""";

Here, readRepositories fetches the last N repositories of the viewer (user).

Step 2: Execute the Query

Use the Query widget from the graphql_flutter package to execute the query. Pass the query string and any variables to the widget:

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

class RepositoryList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(readRepositories),
        variables: {
          'nRepositories': 5,
        },
      ),
      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');
        }

        return ListView.builder(
          itemCount: repositories.length,
          itemBuilder: (context, index) {
            final repository = repositories[index];

            return ListTile(
              title: Text(repository['name'] ?? ''),
              subtitle: Text(repository['url'] ?? ''),
            );
          },
        );
      },
    );
  }
}

In this example:

  • Query widget executes the GraphQL query.
  • The builder function handles different states: loading, error, and data retrieval.
  • If data is available, it displays a list of repositories.

Writing GraphQL Mutations

Mutations are used to modify data on the GraphQL server. Here’s how to write and execute GraphQL mutations in Flutter.

Step 1: Define the Mutation

Create a GraphQL mutation using a String. This mutation specifies the data you want to modify:

const String addStar = """
  mutation AddStar($id: ID!) {
    addStar(input: {starrableId: $id}) {
      starrable {
        viewerHasStarred
      }
    }
  }
""";

Here, addStar adds a star to a repository with the given ID.

Step 2: Execute the Mutation

Use the Mutation widget from the graphql_flutter package to execute the mutation. Pass the mutation string and any variables to the widget:

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

class StarButton extends StatelessWidget {
  final String repositoryId;

  StarButton({required this.repositoryId});

  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(addStar),
        update: (cache, result) {
          return cache;
        },
        onCompleted: (dynamic resultData) {
          print('Starred!');
        },
      ),
      builder: (RunMutation runMutation, QueryResult? result) {
        return ElevatedButton(
          onPressed: () {
            runMutation({
              'id': repositoryId,
            });
          },
          child: Text('Star Repository'),
        );
      },
    );
  }
}

In this example:

  • Mutation widget executes the GraphQL mutation.
  • The builder function provides a runMutation function to trigger the mutation.
  • The onPressed callback calls runMutation with the necessary variables.

Handling Variables and Dynamic Queries/Mutations

GraphQL allows the use of variables for dynamic queries and mutations. Using variables, you can reuse the same query or mutation definition with different values.

Using Variables

const String getUserQuery = """
  query GetUser($userId: ID!) {
    user(id: $userId) {
      id
      name
      email
    }
  }
""";

Query(
  options: QueryOptions(
    document: gql(getUserQuery),
    variables: {
      'userId': 'user123', // Replace with a dynamic user ID
    },
  ),
  builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
    // Handle query results
  },
);

In this case, the $userId variable is dynamically replaced when executing the query.

Best Practices for GraphQL in Flutter

  • Optimize Queries: Request only the necessary data fields to reduce payload size.
  • Handle Errors: Properly handle loading, error, and data states using the builder function.
  • Use Fragments: For complex queries, use fragments to break down the query into reusable parts.
  • Caching: Utilize caching to improve performance and reduce unnecessary network requests.

Conclusion

GraphQL offers significant advantages for data fetching and manipulation in Flutter applications. By using the graphql_flutter package, you can easily write and execute GraphQL queries and mutations, resulting in more efficient and performant apps. Understanding and implementing these concepts will enable you to build robust, data-driven Flutter applications using GraphQL.