Writing GraphQL Queries to Fetch Specific Data and Mutations to Modify Data on the Server in Flutter

In modern app development, GraphQL has emerged as a powerful alternative to REST APIs. It allows clients to request specific data they need and provides mutations to modify data on the server efficiently. Combining Flutter, Google’s UI toolkit, with GraphQL, enables developers to build robust and performant applications. This blog post delves into how to write GraphQL queries to fetch data and mutations to modify data on the server in a 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 often returns fixed data structures, GraphQL allows clients to specify exactly what data they need. This avoids over-fetching and under-fetching, optimizing data transfer.

Why Use GraphQL with Flutter?

  • Efficient Data Fetching: Clients fetch only the data they need, reducing network overhead.
  • Strongly Typed: GraphQL schemas enforce strong typing, catching errors early in development.
  • Real-time Updates: Subscriptions in GraphQL enable real-time data updates, perfect for dynamic applications.
  • Developer Productivity: Tools like GraphiQL and Apollo Client improve the development experience.

Setting Up GraphQL in a Flutter Project

Step 1: Add Dependencies

First, you need to add the necessary dependencies to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.2 # Or the latest version

After adding the dependency, run flutter pub get to install the package.

Step 2: Initialize GraphQL Client

Initialize the GraphQL client with the endpoint of your GraphQL server. You’ll use this client to make queries and mutations.

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(
      link: httpLink,
      cache: GraphQLCache(store: HiveStore()),
    ),
  );

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

  runApp(app);
}

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

Writing GraphQL Queries in Flutter

Fetching data in GraphQL involves writing queries that specify the fields you need. Here’s how to do it in Flutter:

Step 1: Define a GraphQL Query

Define a GraphQL query string. For example, to fetch a list of users with their names and email addresses:


query GetUsers {
  users {
    id
    name
    email
  }
}

Step 2: Use the Query Widget

Use the Query widget from the graphql_flutter package to execute the query and handle the results:

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

class UserList extends StatelessWidget {
  final String getUsersQuery = """
    query GetUsers {
      users {
        id
        name
        email
      }
    }
  """;

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

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

        List? users = result.data?['users'];
        if (users == null) {
          return Text('No users found');
        }

        return ListView.builder(
          itemCount: users.length,
          itemBuilder: (context, index) {
            final user = users[index];
            return ListTile(
              title: Text(user['name']),
              subtitle: Text(user['email']),
            );
          },
        );
      },
    );
  }
}

Explanation:

  • QueryOptions takes the GraphQL query as a string.
  • The builder function handles different states of the query: loading, error, and data.
  • The result data is extracted from result.data and displayed in a ListView.

Writing GraphQL Mutations in Flutter

Modifying data on the server is done through mutations. Here’s how to create and use them in Flutter:

Step 1: Define a GraphQL Mutation

Define a GraphQL mutation string. For example, to create a new user:


mutation CreateUser($name: String!, $email: String!) {
  createUser(name: $name, email: $email) {
    id
    name
    email
  }
}

Step 2: Use the Mutation Widget

Use the Mutation widget to execute the mutation and handle the results:

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

class CreateUserForm extends StatefulWidget {
  @override
  _CreateUserFormState createState() => _CreateUserFormState();
}

class _CreateUserFormState extends State {
  final _formKey = GlobalKey();
  String _name = '';
  String _email = '';

  final String createUserMutation = """
    mutation CreateUser($name: String!, $email: String!) {
      createUser(name: $name, email: $email) {
        id
        name
        email
      }
    }
  """;

  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(createUserMutation),
        onCompleted: (dynamic resultData) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('User created!')),
          );
        },
      ),
      builder: (RunMutation runMutation, QueryResult? result) {
        return Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                decoration: InputDecoration(labelText: 'Name'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter a name';
                  }
                  return null;
                },
                onSaved: (value) => _name = value!,
              ),
              TextFormField(
                decoration: InputDecoration(labelText: 'Email'),
                validator: (value) {
                  if (value == null || value.isEmpty || !value.contains('@')) {
                    return 'Please enter a valid email';
                  }
                  return null;
                },
                onSaved: (value) => _email = value!,
              ),
              ElevatedButton(
                onPressed: () {
                  if (_formKey.currentState!.validate()) {
                    _formKey.currentState!.save();
                    runMutation({
                      'name': _name,
                      'email': _email,
                    });
                  }
                },
                child: Text('Create User'),
              ),
              if (result?.isLoading ?? false)
                CircularProgressIndicator(),
              if (result?.hasException ?? false)
                Text('Error: ${result?.exception.toString()}'),
            ],
          ),
        );
      },
    );
  }
}

Explanation:

  • MutationOptions takes the GraphQL mutation as a string and provides an onCompleted callback.
  • The builder function provides a runMutation function that you can call with the mutation variables.
  • The form fields collect user input, and when the form is submitted, runMutation is called with the collected data.

Advanced GraphQL Techniques in Flutter

Using Fragments

GraphQL fragments allow you to reuse parts of queries or mutations. For example, you can define a user fragment:


fragment UserFields on User {
  id
  name
  email
}

query GetUser($id: ID!) {
  user(id: $id) {
    ...UserFields
  }
}

mutation CreateUser($name: String!, $email: String!) {
  createUser(name: $name, email: $email) {
    ...UserFields
  }
}

In Flutter, you can define the fragment and use it in your queries and mutations:


final String userFragment = """
  fragment UserFields on User {
    id
    name
    email
  }
""";

final String getUserQuery = """
  query GetUser($id: ID!) {
    user(id: $id) {
      ...UserFields
    }
  }
  $userFragment
""";

final String createUserMutation = """
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      ...UserFields
    }
  }
  $userFragment
""";

Real-Time Updates with Subscriptions

GraphQL subscriptions enable real-time data updates. Here’s how to set up a subscription in Flutter:

Step 1: Configure WebSocket Link

First, you need to configure a WebSocket link to handle subscriptions:


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

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

  final WebSocketLink websocketLink = WebSocketLink(
    'ws://your-graphql-endpoint.com/graphql',
    config: SocketClientConfig(
      autoReconnect: true,
      inactivityTimeout: Duration(seconds: 30),
    ),
  );

  final Link link = Link.split((request) => request.isSubscription, websocketLink, httpLink);

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

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

  runApp(app);
}
Step 2: Define a Subscription Query

Define a GraphQL subscription query:


subscription OnNewUser {
  newUser {
    id
    name
    email
  }
}
Step 3: Use the Subscription Widget

Use the Subscription widget to listen for real-time updates:


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

class NewUserList extends StatelessWidget {
  final String newUserSubscription = """
    subscription OnNewUser {
      newUser {
        id
        name
        email
      }
    }
  """;

  @override
  Widget build(BuildContext context) {
    return Subscription(
      options: SubscriptionOptions(
        document: gql(newUserSubscription),
      ),
      builder: (QueryResult result) {
        if (result.hasException) {
          return Text("Error: ${result.exception.toString()}");
        }

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

        final newUser = result.data?['newUser'];
        if (newUser == null) {
          return Text('No new users');
        }

        return ListTile(
          title: Text(newUser['name']),
          subtitle: Text(newUser['email']),
        );
      },
    );
  }
}

Conclusion

GraphQL offers a robust and efficient way to fetch and modify data in Flutter applications. By writing specific queries and mutations, you can optimize data transfer and improve app performance. Integrating advanced techniques such as fragments and subscriptions further enhances the capabilities of your Flutter applications. With the graphql_flutter package, implementing GraphQL in Flutter is straightforward, allowing you to build scalable and performant applications.