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