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, you fetch multiple resources in a single request, reducing network calls and improving app performance significantly.
// Simple GraphQL query example
const String getUsers = '''
query GetUsers {
users {
id
name
email
}
}
''';
Key GraphQL Concepts
Queries
Fetch data from the server. Queries let you request specific fields from your API, getting only what you need without over-fetching or under-fetching data.
query {
user(id: "1") {
name
email
}
}
Mutations
Modify data on the server. Mutations create, update, or delete data, similar to POST, PUT, and DELETE in REST but with more flexibility and precision.
mutation {
createUser(name: "John", email: "[email protected]") {
id
name
}
}
Subscriptions
Real-time data updates via WebSocket. Subscriptions push data changes to your app instantly, perfect for chat apps, live feeds, or real-time notifications.
subscription {
messageAdded {
id
text
user
}
}
Fragments
Reusable query pieces for cleaner code. Fragments define reusable sets of fields, reducing duplication and making your queries easier to maintain and update.
fragment UserInfo on User {
id
name
email
}
🔹 Setting Up GraphQL
Add the graphql_flutter package to your project:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
graphql_flutter: ^5.1.0
// main.dart - Initialize GraphQL client
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',
);
ValueNotifier<GraphQLClient> client = ValueNotifier(
GraphQLClient(
link: httpLink,
cache: GraphQLCache(store: HiveStore()),
),
);
runApp(
GraphQLProvider(
client: client,
child: MyApp(),
),
);
}
Setup steps:
1. Add graphql_flutter to pubspec.yaml
2. Run:
flutter pub get
3. Initialize GraphQL client
4. Wrap app with GraphQLProvider
🔹 Fetching Data with Query
Use Query widget to fetch and display data:
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
const String getUsersQuery = '''
query GetUsers {
users {
id
name
email
avatar
}
}
''';
class UsersScreen extends StatelessWidget {
@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 Center(
child: 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(
leading: CircleAvatar(
backgroundImage: NetworkImage(user['avatar']),
),
title: Text(user['name']),
subtitle: Text(user['email']),
);
},
);
},
),
);
}
}
Features:
✓ Automatic loading states
✓ Error handling built-in
✓ Auto-refresh every 10 seconds
✓ Caching for better performance
🔹 Modifying Data with Mutation
Use Mutation widget to create or update data:
const String createUserMutation = '''
mutation CreateUser(\$name: String!, \$email: String!) {
createUser(name: \$name, email: \$email) {
id
name
email
}
}
''';
class CreateUserScreen extends StatelessWidget {
final nameController = TextEditingController();
final emailController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Create User')),
body: Mutation(
options: MutationOptions(
document: gql(createUserMutation),
onCompleted: (dynamic resultData) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('User created successfully!')),
);
},
),
builder: (RunMutation runMutation, QueryResult? result) {
return Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: nameController,
decoration: InputDecoration(labelText: 'Name'),
),
SizedBox(height: 16),
TextField(
controller: emailController,
decoration: InputDecoration(labelText: 'Email'),
),
SizedBox(height: 24),
ElevatedButton(
onPressed: () {
runMutation({
'name': nameController.text,
'email': emailController.text,
});
},
child: result?.isLoading == true
? CircularProgressIndicator(color: Colors.white)
: Text('Create User'),
),
if (result?.hasException == true)
Padding(
padding: EdgeInsets.only(top: 16),
child: Text(
'Error: ${result!.exception.toString()}',
style: TextStyle(color: Colors.red),
),
),
],
),
);
},
),
);
}
}
What happens:
1. User fills in name and email
2. Mutation sends data to server
3. Server creates new user
4. Success message displayed
🔹 Query with Variables
Pass dynamic values to your queries:
const String getUserQuery = '''
query GetUser(\$id: ID!) {
user(id: \$id) {
id
name
email
posts {
id
title
content
}
}
}
''';
class UserDetailScreen extends StatelessWidget {
final String userId;
UserDetailScreen({required this.userId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('User Details')),
body: Query(
options: QueryOptions(
document: gql(getUserQuery),
variables: {'id': userId},
),
builder: (QueryResult result, {fetchMore, refetch}) {
if (result.isLoading) {
return Center(child: CircularProgressIndicator());
}
final user = result.data!['user'];
return SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user['name'],
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
Text(user['email']),
SizedBox(height: 24),
Text(
'Posts',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
...List.generate(
user['posts'].length,
(index) => Card(
child: ListTile(
title: Text(user['posts'][index]['title']),
subtitle: Text(user['posts'][index]['content']),
),
),
),
],
),
);
},
),
);
}
}
🔹 Real-time Updates with Subscription
Listen to real-time data changes:
const String messageSubscription = '''
subscription OnMessageAdded {
messageAdded {
id
text
user {
name
}
createdAt
}
}
''';
class ChatScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Live Chat')),
body: Subscription(
options: SubscriptionOptions(
document: gql(messageSubscription),
),
builder: (QueryResult result) {
if (result.hasException) {
return Center(child: Text('Error: ${result.exception}'));
}
if (result.isLoading) {
return Center(child: Text('Connecting...'));
}
final message = result.data!['messageAdded'];
return ListView(
children: [
ListTile(
title: Text(message['user']['name']),
subtitle: Text(message['text']),
trailing: Text(message['createdAt']),
),
],
);
},
),
);
}
}
Real-time features:
✓ Instant message updates
✓ No manual refresh needed
✓ WebSocket connection
✓ Live data synchronization