Flutter GraphQL

Efficient data fetching with GraphQL queries

🚀 What is GraphQL?

GraphQL is a query language for APIs that lets you request exactly the data you need. Unlike REST, GraphQL uses a single endpoint and allows flexible queries, reducing over-fetching and under-fetching.


// GraphQL query
query {
  user(id: 1) {
    name
    email
  }
}
                                    

Output:

{"user": {"name": "John", "email": "[email protected]"}}

GraphQL Operations

🔍

Query

Fetch data from server

query GetUsers {
  users {
    id
    name
  }
}
✏️

Mutation

Modify data on server

mutation CreateUser {
  createUser(name: "John") {
    id
    name
  }
}
📡

Subscription

Real-time data updates

subscription OnUserAdded {
  userAdded {
    id
    name
  }
}
🎯

Fragments

Reusable query parts

fragment UserInfo on User {
  id
  name
  email
}

🔹 Setting Up GraphQL

Add the graphql_flutter package:

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.2

GraphQL vs REST:

  • Single Endpoint: One URL for all operations
  • Flexible Queries: Request exactly what you need
  • Strongly Typed: Schema defines data structure
  • Real-time: Built-in subscription support

🔹 Initialize GraphQL Client

Set up the GraphQL client in your app:

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

void main() async {
  await initHiveForFlutter();
  
  final HttpLink httpLink = HttpLink(
    'https://api.example.com/graphql',
  );

  final AuthLink authLink = AuthLink(
    getToken: () async => 'Bearer YOUR_TOKEN',
  );

  final Link link = authLink.concat(httpLink);

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

  runApp(
    GraphQLProvider(
      client: client,
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'GraphQL Demo',
      home: UserListScreen(),
    );
  }
}

Output:

✓ GraphQL client initialized

🔹 GraphQL Query Example

Fetch data using GraphQL queries:

import 'package:graphql_flutter/graphql_flutter.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Users')),
      body: Query(
        options: QueryOptions(
          document: gql(getUsersQuery),
          pollInterval: Duration(seconds: 10),
        ),
        builder: (QueryResult result, {fetchMore, refetch}) {
          if (result.hasException) {
            return Text('Error: ${result.exception.toString()}');
          }

          if (result.isLoading) {
            return Center(child: CircularProgressIndicator());
          }

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

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

Output:

🔹 GraphQL Mutation Example

Create or update data with mutations:

class CreateUserScreen extends StatelessWidget {
  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) {
          print('User created: ${resultData['createUser']['name']}');
        },
      ),
      builder: (RunMutation runMutation, QueryResult? result) {
        return ElevatedButton(
          onPressed: () {
            runMutation({
              'name': 'John Doe',
              'email': '[email protected]',
            });
          },
          child: Text('Create User'),
        );
      },
    );
  }
}

Output:

✓ User created: John Doe

ID: 123

🔹 Query with Variables

Pass dynamic values to queries:

class UserDetailScreen extends StatelessWidget {
  final int userId;

  UserDetailScreen({required this.userId});

  final String getUserQuery = '''
    query GetUser(\$id: Int!) {
      user(id: \$id) {
        id
        name
        email
        posts {
          id
          title
        }
      }
    }
  ''';

  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(getUserQuery),
        variables: {'id': userId},
      ),
      builder: (QueryResult result, {fetchMore, refetch}) {
        if (result.isLoading) {
          return CircularProgressIndicator();
        }

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

        return Column(
          children: [
            Text('Name: ${user['name']}'),
            Text('Email: ${user['email']}'),
            Text('Posts: ${user['posts'].length}'),
          ],
        );
      },
    );
  }
}

Output:

Name: John Doe

Email: [email protected]

Posts: 5

🔹 GraphQL Subscriptions

Listen to real-time updates:

class RealtimeMessagesScreen extends StatelessWidget {
  final String messageSubscription = '''
    subscription OnMessageAdded {
      messageAdded {
        id
        text
        user {
          name
        }
      }
    }
  ''';

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

        if (result.isLoading) {
          return Text('Waiting for messages...');
        }

        final message = result.data?['messageAdded'];

        return ListTile(
          title: Text(message['text']),
          subtitle: Text('From: ${message['user']['name']}'),
        );
      },
    );
  }
}

Output (Real-time):

Hello everyone!

From: John Doe

How are you?

From: Jane Smith

🔹 Error Handling

Handle GraphQL errors gracefully:

Query(
  options: QueryOptions(
    document: gql(getUsersQuery),
    errorPolicy: ErrorPolicy.all,
  ),
  builder: (QueryResult result, {fetchMore, refetch}) {
    if (result.hasException) {
      // Check for network errors
      if (result.exception?.linkException != null) {
        return Text('Network error: Check your connection');
      }
      
      // Check for GraphQL errors
      if (result.exception?.graphqlErrors != null) {
        final errors = result.exception!.graphqlErrors;
        return Text('GraphQL error: ${errors[0].message}');
      }
      
      return Text('Unknown error occurred');
    }

    // Success - display data
    return ListView(...);
  },
);

Possible Outputs:

✗ Network error: Check your connection

⚠ GraphQL error: User not found

🧠 Test Your Knowledge

What is the main advantage of GraphQL over REST?