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:
🔹 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