Writing GraphQL Queries and Mutations in Flutter

GraphQL has emerged as a powerful alternative to REST APIs, offering more flexibility and efficiency in data fetching. Flutter, Google’s UI toolkit, provides robust support for integrating GraphQL into your applications. This blog post guides you through writing GraphQL queries and mutations in Flutter, covering essential concepts, setup, and practical examples.

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. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.

Key Benefits of GraphQL:

  • Fetch Exactly What You Need: Clients can request specific fields, avoiding over-fetching of data.
  • Reduced Network Requests: Efficiently fetch data with a single API request.
  • Strong Typing: Ensures type safety and improves code maintainability.
  • Introspection: Allows clients to explore the API’s capabilities.

Why Use GraphQL with Flutter?

Integrating GraphQL into your Flutter apps can lead to:

  • Improved Performance: Fetch only the required data, reducing bandwidth usage.
  • Enhanced Flexibility: Easily adapt data requirements as your UI evolves.
  • Better Development Experience: Type safety and auto-completion features improve productivity.

Setting Up GraphQL in Flutter

Step 1: Add Dependencies

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

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.2

Run flutter pub get to install the dependencies.

Step 2: Configure the GraphQL Client

Initialize the GraphQL client and provide the endpoint of your GraphQL server.

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

  var app = GraphQLProvider(
    client: client,
    child: MaterialApp(
      title: 'GraphQL Flutter App',
      home: Scaffold(body: HomeScreen()),
    ),
  );

  runApp(app);
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('GraphQL Flutter App'));
  }
}

Writing GraphQL Queries in Flutter

Example Query: Fetching a User’s Name

Here’s how to write a simple GraphQL query to fetch a user’s name:

query GetUserName {
  user(id: "123") {
    name
  }
}

Using Query Widget

In Flutter, you can use the Query widget to execute GraphQL queries. It automatically rebuilds the UI when the data changes.

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

class UserName extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(
          '''
          query GetUserName {
            user(id: "123") {
              name
            }
          }
          ''',
        ),
      ),
      builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
        if (result.hasException) {
          return Text(result.exception.toString());
        }

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

        final userName = result.data?['user']['name'];

        return Text('User Name: $userName');
      },
    );
  }
}

Writing GraphQL Mutations in Flutter

Example Mutation: Updating a User’s Name

A mutation in GraphQL allows you to modify data. Here’s a sample mutation to update a user’s name:

mutation UpdateUserName($id: ID!, $name: String!) {
  updateUser(id: $id, name: $name) {
    id
    name
  }
}

Using Mutation Widget

In Flutter, you can use the Mutation widget to execute GraphQL mutations.

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

class UpdateUserName extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(
          '''
          mutation UpdateUserName($id: ID!, $name: String!) {
            updateUser(id: $id, name: $name) {
              id
              name
            }
          }
          ''',
        ),
        update: (cache, result) {
          return cache;
        },
        onCompleted: (dynamic resultData) {
          print('User updated: $resultData');
        },
      ),
      builder: (RunMutation runMutation, QueryResult result) {
        return ElevatedButton(
          onPressed: () {
            runMutation({
              "id": "123",
              "name": "New User Name",
            });
          },
          child: const Text('Update User Name'),
        );
      },
    );
  }
}

Handling Variables

Variables are a crucial part of GraphQL for dynamic queries and mutations. They allow you to pass arguments to your queries and mutations.

Example: Query with Variables

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
  }
}
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class UserDetails extends StatelessWidget {
  final String userId;

  UserDetails({required this.userId});

  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(
          '''
          query GetUser($id: ID!) {
            user(id: $id) {
              id
              name
              email
            }
          }
          ''',
        ),
        variables: {
          'id': userId,
        },
      ),
      builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
        if (result.hasException) {
          return Text(result.exception.toString());
        }

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

        final user = result.data?['user'];
        final userName = user['name'];
        final userEmail = user['email'];

        return Column(
          children: [
            Text('User Name: $userName'),
            Text('User Email: $userEmail'),
          ],
        );
      },
    );
  }
}

Error Handling

Properly handling errors is crucial for a robust application. The graphql_flutter package provides error information in the QueryResult.

Query(
  options: QueryOptions(
    document: gql(
      '''
      query GetUserName {
        user(id: "123") {
          name
        }
      }
      ''',
    ),
  ),
  builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
    if (result.hasException) {
      return Text('Error: ${result.exception}');
    }

    // ... rest of the code
  },
)

Conclusion

Writing GraphQL queries and mutations in Flutter offers a more efficient and flexible way to handle API requests compared to traditional REST. With the graphql_flutter package, you can easily integrate GraphQL into your Flutter applications, leveraging its powerful features and improving the overall development experience. By using the Query and Mutation widgets, handling variables, and implementing robust error handling, you can build robust and performant Flutter apps with GraphQL.