As mobile development evolves, so do the methods for data retrieval and manipulation. REST APIs have long been the standard, but GraphQL offers a powerful alternative that provides more flexibility and efficiency. This article will guide you through working with GraphQL APIs in Flutter, covering setup, queries, mutations, and more.
What is GraphQL?
GraphQL is a query language for your API and a server-side runtime for executing queries. Unlike REST, where the server dictates the data structure, GraphQL allows clients to request only the data they need, resulting in fewer over-fetching and under-fetching problems. It exposes a single endpoint and uses a strong type system to define data structure and operations.
Why Use GraphQL Over REST in Flutter?
- Reduced Data Transfer: Fetch only what you need.
- Avoid Over-Fetching and Under-Fetching: Precise data retrieval reduces wasted bandwidth.
- Strong Type System: Prevents runtime errors with schema validation.
- Introspection: Easily discover available data and operations.
- Single Endpoint: Simplified API interactions.
Setting Up GraphQL in Your Flutter Project
Before diving into the code, you’ll need to set up GraphQL dependencies in your Flutter project.
Step 1: Add Dependencies
Add the graphql_flutter
package to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
graphql_flutter: ^5.1.0 # Use the latest version
Then, run flutter pub get
to install the dependencies.
Step 2: Configure the GraphQL Client
Create a GraphQL client instance to interact with the API endpoint. Here’s how:
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
void main() {
final HttpLink httpLink = HttpLink(
'https://your-graphql-endpoint.com/graphql', // Replace with your GraphQL endpoint
);
final ValueNotifier client = ValueNotifier(
GraphQLClient(
link: httpLink,
cache: GraphQLCache(store: InMemoryStore()),
),
);
var app = GraphQLProvider(
client: client,
child: MaterialApp(
title: 'Flutter GraphQL Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('GraphQL Demo'),
),
body: const HomePage(),
),
),
);
runApp(app);
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Center(
child: Text('GraphQL Demo'),
);
}
}
Explanation:
- HttpLink: Specifies the GraphQL endpoint URL.
- GraphQLClient: Handles communication with the GraphQL server.
- GraphQLProvider: Makes the GraphQL client accessible throughout your app.
- GraphQLCache: Caches GraphQL responses to improve performance.
Performing GraphQL Queries in Flutter
To fetch data from your GraphQL API, you’ll use the Query
widget.
Step 1: Define Your GraphQL Query
Construct a GraphQL query to retrieve the desired data. For example, to fetch a list of users:
query GetUsers {
users {
id
name
email
}
}
Step 2: Use the Query Widget
In your Flutter widget, use the Query
widget to execute the GraphQL query and display the data:
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class UserList extends StatelessWidget {
const UserList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const String getUsersQuery = """
query GetUsers {
users {
id
name
email
}
}
""";
return Query(
options: QueryOptions(
document: gql(getUsersQuery),
),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return const CircularProgressIndicator();
}
final userList = result.data?['users'] as List;
return ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
final user = userList[index];
return ListTile(
title: Text(user['name']),
subtitle: Text(user['email']),
);
},
);
},
);
}
}
Explanation:
- Query Widget: Executes the GraphQL query.
- QueryOptions: Specifies the GraphQL document (query).
- builder: A function that builds the UI based on the query result.
- QueryResult: Contains the query result data, loading state, and any errors.
Performing GraphQL Mutations in Flutter
Mutations are used to modify data on the server. Use the Mutation
widget to perform mutations.
Step 1: Define Your GraphQL Mutation
Construct a GraphQL mutation to modify data. For example, to create a new user:
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
Step 2: Use the Mutation Widget
Implement the Mutation
widget to execute the mutation and handle the response:
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class CreateUserForm extends StatefulWidget {
const CreateUserForm({Key? key}) : super(key: key);
@override
_CreateUserFormState createState() => _CreateUserFormState();
}
class _CreateUserFormState extends State {
final TextEditingController nameController = TextEditingController();
final TextEditingController emailController = TextEditingController();
@override
Widget build(BuildContext context) {
const String createUserMutation = """
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
""";
return Mutation(
options: MutationOptions(
document: gql(createUserMutation),
onCompleted: (dynamic result) {
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('User created successfully!')),
);
}
},
),
builder: (RunMutation runMutation, QueryResult? result) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextFormField(
controller: nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
TextFormField(
controller: emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
ElevatedButton(
onPressed: () {
runMutation({
'name': nameController.text,
'email': emailController.text,
});
},
child: const Text('Create User'),
),
if (result?.isLoading ?? false)
const CircularProgressIndicator(),
if (result?.hasException ?? false)
Text(result!.exception.toString()),
],
),
);
},
);
}
@override
void dispose() {
nameController.dispose();
emailController.dispose();
super.dispose();
}
}
Explanation:
- Mutation Widget: Executes the GraphQL mutation.
- MutationOptions: Specifies the GraphQL document (mutation) and the
onCompleted
callback. - RunMutation: A function that executes the mutation with the provided variables.
Handling Errors and Loading States
GraphQL APIs in Flutter return a QueryResult
object that contains information about loading states and any errors that occur.
Loading State
Query(
options: QueryOptions(document: gql(query)),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
if (result.isLoading) {
return const CircularProgressIndicator();
}
// ...
},
);
Error Handling
Query(
options: QueryOptions(document: gql(query)),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Text('Error: ${result.exception}');
}
// ...
},
);
Fetching More Data
For paginated data, the FetchMore
option allows you to retrieve additional results.
Query(
options: QueryOptions(document: gql(query)),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
// ...
ElevatedButton(
onPressed: () {
fetchMore!(FetchMoreOptions(
variables: {'cursor': nextCursor},
));
},
child: const Text('Load More'),
);
},
);
Conclusion
GraphQL APIs offer a modern and efficient way to handle data in Flutter applications. By using packages like graphql_flutter
, developers can easily perform queries and mutations while leveraging features like strong typing and reduced data transfer. This makes GraphQL a viable and often superior alternative to REST, enhancing app performance and developer productivity.