Back to Blog
Flutter Development

GraphQL Integration in Flutter

Step-by-step guide to implementing GraphQL in Flutter applications using the graphql_flutter package.

D
Don Wilson
Mobile Developer
May 15, 2025
9 min read
GraphQL Integration in Flutter

GraphQL provides a powerful alternative to REST APIs, offering flexible data fetching and reducing over-fetching. Learn how to integrate GraphQL seamlessly into your Flutter applications.

Why GraphQL?

GraphQL offers several advantages over traditional REST APIs:

  • Precise data fetching: Request exactly what you need
  • Single endpoint: All data accessible from one URL
  • Strongly typed: Built-in type system and validation
  • Real-time updates: Built-in subscription support

Setup

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

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^5.1.0

Configuration

Set up the GraphQL client:

import 'package:graphql_flutter/graphql_flutter.dart';

class GraphQLConfig {
  static HttpLink httpLink = HttpLink(
    'https://api.example.com/graphql',
  );
  
  static AuthLink authLink = AuthLink(
    getToken: () async => 'Bearer YOUR_TOKEN',
  );
  
  static Link link = authLink.concat(httpLink);
  
  static ValueNotifier<GraphQLClient> initializeClient() {
    return ValueNotifier(
      GraphQLClient(
        link: link,
        cache: GraphQLCache(store: InMemoryStore()),
      ),
    );
  }
}

Queries

Fetch data with queries:

const String getUserQuery = r'''
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
      posts {
        id
        title
        content
      }
    }
  }
''';

class UserProfile extends StatelessWidget {
  final String userId;
  
  UserProfile({required this.userId});
  
  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(getUserQuery),
        variables: {'id': userId},
      ),
      builder: (QueryResult result, {refetch, fetchMore}) {
        if (result.isLoading) {
          return CircularProgressIndicator();
        }
        
        if (result.hasException) {
          
        }
        
        final user = result.data?['user'];
        
        return Column(
          children: [
            Text(user['name']),
            Text(user['email']),
            ...user['posts'].map((post) => PostWidget(post)).toList(),
          ],
        );
      },
    );
  }
}

Mutations

Modify data with mutations:

const String updateUserMutation = r'''
  mutation UpdateUser($id: ID!, $name: String!, $email: String!) {
    updateUser(id: $id, name: $name, email: $email) {
      id
      name
      email
      updatedAt
    }
  }
''';

class UpdateUserForm extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(updateUserMutation),
        onCompleted: (dynamic resultData) {
          print('User updated successfully');
        },
      ),
      builder: (RunMutation runMutation, QueryResult? result) {
        return ElevatedButton(
          onPressed: () {
            runMutation({
              'id': '1',
              'name': 'John Doe',
              'email': 'john@example.com',
            });
          },
          child: Text('Update User'),
        );
      },
    );
  }
}

Subscriptions

Listen to real-time updates:

const String messageSubscription = r'''
  subscription OnMessageAdded {
    messageAdded {
      id
      content
      author {
        id
        name
      }
      createdAt
    }
  }
''';

class MessageList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Subscription(
      options: SubscriptionOptions(
        document: gql(messageSubscription),
      ),
      builder: (QueryResult result) {
        if (result.hasException) {
          return Text('Error');
        }
        
        if (result.isLoading) {
          return CircularProgressIndicator();
        }
        
        final message = result.data?['messageAdded'];
        
        return MessageWidget(message);
      },
    );
  }
}

Error Handling

Implement robust error handling:

class GraphQLErrorHandler {
  static String getErrorMessage(OperationException exception) {
    if (exception.linkException != null) {
      if (exception.linkException is ServerException) {
        return 'Server error occurred';
      }
      if (exception.linkException is NetworkException) {
        return 'Network error. Please check your connection';
      }
    }
    
    if (exception.graphqlErrors.isNotEmpty) {
      return exception.graphqlErrors.first.message;
    }
    
    return 'An unknown error occurred';
  }
}

Caching

Configure caching for better performance:

final cache = GraphQLCache(
  store: HiveStore(),
);

final client = GraphQLClient(
  link: link,
  cache: cache,
  defaultPolicies: DefaultPolicies(
    query: Policies(
      fetch: FetchPolicy.cacheFirst,
    ),
    mutate: Policies(
      fetch: FetchPolicy.networkOnly,
    ),
  ),
);

Best Practices

  • Use fragments: Reuse common field selections
  • Implement pagination: Handle large datasets efficiently
  • Error boundaries: Gracefully handle errors
  • Optimize queries: Request only necessary fields
  • Use variables: Make queries dynamic and reusable

Conclusion

GraphQL integration in Flutter provides a powerful way to manage data fetching. With proper implementation, you can build efficient, scalable applications with excellent user experiences.

Remember:

  • Start simple and add complexity as needed
  • Leverage caching for better performance
  • Handle errors gracefully
  • Use subscriptions for real-time features

Tags

FlutterGraphQLAPI Integration

Share this article