GraphQL has emerged as a powerful alternative to traditional REST APIs, offering more flexibility and efficiency in data fetching and manipulation. Flutter developers can leverage GraphQL to build performant and data-efficient mobile applications. This article explores how to write GraphQL queries and mutations in Flutter to fetch and modify data seamlessly.
What is GraphQL?
GraphQL is a query language for your API and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.
Why Use GraphQL in Flutter?
- Efficient Data Fetching: GraphQL allows you to request specific data fields, reducing over-fetching and under-fetching.
- Strongly Typed Schema: The GraphQL schema ensures type safety and improves developer understanding of the API.
- Single Endpoint: Unlike REST, GraphQL uses a single endpoint for all queries, simplifying API management.
Setting Up GraphQL in Flutter
To use GraphQL in your Flutter project, you’ll need to set up the necessary dependencies and configure a GraphQL client.
Step 1: Add Dependencies
Add the graphql_flutter package to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
graphql_flutter: ^5.1.0
Then, run flutter pub get to install the package.
Step 2: Configure GraphQL Client
Create a GraphQL client using GraphQLClient. You’ll need to specify the GraphQL endpoint and configure the cache:
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
void main() {
final HttpLink httpLink = HttpLink(
'https://your-graphql-endpoint.com/graphql',
);
final ValueNotifier client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()),
link: httpLink,
),
);
var app = GraphQLProvider(
client: client,
child: MyApp(),
);
runApp(app);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GraphQL Flutter App',
home: Scaffold(
appBar: AppBar(
title: Text('GraphQL Example'),
),
body: Center(
child: Text('GraphQL Data'),
),
),
);
}
}
Replace 'https://your-graphql-endpoint.com/graphql' with your actual GraphQL endpoint.
Writing GraphQL Queries
Queries are used to fetch data from the GraphQL server. Here’s how to write and execute GraphQL queries in Flutter.
Step 1: Define the Query
Create a GraphQL query using a String. This query specifies the data you want to retrieve:
const String readRepositories = """
query ReadRepositories($nRepositories: Int!) {
viewer {
repositories(last: $nRepositories) {
nodes {
id
name
url
}
}
}
}
""";
Here, readRepositories fetches the last N repositories of the viewer (user).
Step 2: Execute the Query
Use the Query widget from the graphql_flutter package to execute the query. Pass the query string and any variables to the widget:
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class RepositoryList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Query(
options: QueryOptions(
document: gql(readRepositories),
variables: {
'nRepositories': 5,
},
),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return CircularProgressIndicator();
}
List? repositories = result.data?['viewer']['repositories']['nodes'];
if (repositories == null) {
return Text('No repositories');
}
return ListView.builder(
itemCount: repositories.length,
itemBuilder: (context, index) {
final repository = repositories[index];
return ListTile(
title: Text(repository['name'] ?? ''),
subtitle: Text(repository['url'] ?? ''),
);
},
);
},
);
}
}
In this example:
Querywidget executes the GraphQL query.- The
builderfunction handles different states: loading, error, and data retrieval. - If data is available, it displays a list of repositories.
Writing GraphQL Mutations
Mutations are used to modify data on the GraphQL server. Here’s how to write and execute GraphQL mutations in Flutter.
Step 1: Define the Mutation
Create a GraphQL mutation using a String. This mutation specifies the data you want to modify:
const String addStar = """
mutation AddStar($id: ID!) {
addStar(input: {starrableId: $id}) {
starrable {
viewerHasStarred
}
}
}
""";
Here, addStar adds a star to a repository with the given ID.
Step 2: Execute the Mutation
Use the Mutation widget from the graphql_flutter package to execute the mutation. Pass the mutation string and any variables to the widget:
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class StarButton extends StatelessWidget {
final String repositoryId;
StarButton({required this.repositoryId});
@override
Widget build(BuildContext context) {
return Mutation(
options: MutationOptions(
document: gql(addStar),
update: (cache, result) {
return cache;
},
onCompleted: (dynamic resultData) {
print('Starred!');
},
),
builder: (RunMutation runMutation, QueryResult? result) {
return ElevatedButton(
onPressed: () {
runMutation({
'id': repositoryId,
});
},
child: Text('Star Repository'),
);
},
);
}
}
In this example:
Mutationwidget executes the GraphQL mutation.- The
builderfunction provides arunMutationfunction to trigger the mutation. - The
onPressedcallback callsrunMutationwith the necessary variables.
Handling Variables and Dynamic Queries/Mutations
GraphQL allows the use of variables for dynamic queries and mutations. Using variables, you can reuse the same query or mutation definition with different values.
Using Variables
const String getUserQuery = """
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
email
}
}
""";
Query(
options: QueryOptions(
document: gql(getUserQuery),
variables: {
'userId': 'user123', // Replace with a dynamic user ID
},
),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
// Handle query results
},
);
In this case, the $userId variable is dynamically replaced when executing the query.
Best Practices for GraphQL in Flutter
- Optimize Queries: Request only the necessary data fields to reduce payload size.
- Handle Errors: Properly handle loading, error, and data states using the
builderfunction. - Use Fragments: For complex queries, use fragments to break down the query into reusable parts.
- Caching: Utilize caching to improve performance and reduce unnecessary network requests.
Conclusion
GraphQL offers significant advantages for data fetching and manipulation in Flutter applications. By using the graphql_flutter package, you can easily write and execute GraphQL queries and mutations, resulting in more efficient and performant apps. Understanding and implementing these concepts will enable you to build robust, data-driven Flutter applications using GraphQL.