Flutter, Google’s UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, is becoming increasingly popular for its efficiency and beautiful user interfaces. When it comes to interacting with data, GraphQL has emerged as a powerful alternative to traditional REST APIs, offering more flexibility and efficiency in data fetching. In Flutter, the graphql_flutter package provides a robust and streamlined way to interact with GraphQL endpoints. This article delves into how to effectively use the graphql_flutter package to perform various GraphQL operations in your Flutter application.
What is GraphQL?
GraphQL is a query language for your API and a server-side runtime for executing those queries. Unlike REST, which exposes multiple endpoints and often leads to over-fetching or under-fetching of data, GraphQL allows clients to request exactly the data they need and nothing more. This precision reduces network overhead and improves app performance.
Why Use graphql_flutter?
- Simplified Integration: Provides a clean and intuitive API for interacting with GraphQL servers.
- Caching: Offers built-in caching mechanisms to reduce network requests and improve performance.
- Automatic Code Generation: Supports generating type-safe models from GraphQL schemas to avoid manual parsing and increase code safety.
- Subscription Support: Enables real-time updates using GraphQL subscriptions for building reactive applications.
Setting Up Your Flutter Project
Step 1: Add the Dependency
To start using graphql_flutter, you need to add it to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
graphql_flutter: ^5.1.0 # Use the latest version
Then, run flutter pub get in your terminal to install the package.
Step 2: Configure GraphQL Client
Next, configure the GraphQL client with the URL of your GraphQL endpoint and optionally, an HTTP link with authorization headers if required. Wrap your app with the GraphQLProvider widget.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
void main() {
final HttpLink httpLink = HttpLink(
'https://your-graphql-endpoint/graphql', // Replace with your GraphQL endpoint URL
);
// Optionally add authentication headers
final AuthLink authLink = AuthLink(
getToken: () async => 'Bearer YOUR_PERSONAL_ACCESS_TOKEN', // Replace with your token
);
final Link link = authLink.concat(httpLink);
final ValueNotifier client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()),
link: link,
),
);
var app = GraphQLProvider(
client: client,
child: MyApp(),
);
runApp(app);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter GraphQL Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'GraphQL Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text('GraphQL Flutter Demo'),
),
);
}
}
This setup ensures that the GraphQL client is accessible throughout your application via the GraphQLProvider.
Performing Queries
Fetching data is a common task. The graphql_flutter package provides the Query widget to perform GraphQL queries.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class GraphQLQueryDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String readRepositories = """
query ReadRepositories($nRepositories: Int!) {
viewer {
repositories(last: $nRepositories) {
nodes {
id
name
description
url
}
}
}
}
""";
return Query(
options: QueryOptions(
document: gql(readRepositories),
variables: {
'nRepositories': 10,
},
),
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 found');
}
return ListView.builder(
itemCount: repositories.length,
itemBuilder: (context, index) {
final repository = repositories[index];
return ListTile(
title: Text(repository['name'] ?? 'No Name'),
subtitle: Text(repository['description'] ?? 'No Description'),
onTap: () {
// Handle repository tap
},
);
},
);
},
);
}
}
In this example:
- A GraphQL query is defined to fetch the last 10 repositories of the viewer.
- The
Querywidget executes the query and rebuilds the UI based on the query result. - Error, loading, and success states are handled to provide appropriate feedback to the user.
Executing Mutations
To modify data, use the Mutation widget. It allows you to execute GraphQL mutations, such as creating or updating records.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class GraphQLMutationDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String addStar = """
mutation AddStar($starrableId: ID!) {
addStar(input: {starrableId: $starrableId}) {
starrable {
id
viewerHasStarred
}
}
}
""";
return Mutation(
options: MutationOptions(
document: gql(addStar),
onCompleted: (dynamic result) {
print('Mutation completed: $result');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Starred!'),
));
},
),
builder: (RunMutation runMutation, QueryResult? result) {
return ElevatedButton(
onPressed: () {
runMutation({
'starrableId': 'MDEwOlJlcG9zaXRvcnkxMjM0NTY3ODk=', // Replace with an actual starrable ID
});
},
child: Text('Star Repository'),
);
},
);
}
}
Key points:
- The
Mutationwidget requires a GraphQL mutation string. - The
runMutationfunction is called when the button is pressed, triggering the mutation. - The
onCompletedcallback handles the result after the mutation is successfully executed.
Using Subscriptions for Real-Time Updates
GraphQL subscriptions allow clients to receive real-time updates from the server. This is useful for building features like live comments or real-time notifications.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class GraphQLSubscriptionDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String subscribeToComments = """
subscription SubscribeToComments($repoFullName: String!) {
github(repoFullName: $repoFullName) {
commentAdded {
id
content
}
}
}
""";
return Subscription(
options: SubscriptionOptions(
document: gql(subscribeToComments),
variables: {
'repoFullName': 'flutter/flutter', // Example repo
},
),
builder: (BuildContext context,
SubscriptionResult
Explanation:
- The
Subscriptionwidget sets up a real-time connection to the GraphQL server. - The
builderfunction updates the UI whenever a new comment is received. - Loading and error states are handled to provide a smooth user experience.
Caching
graphql_flutter supports caching query results, reducing the need to make repeated requests for the same data. By default, it uses an in-memory cache. For persistent caching, you can use HiveStore.
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()), // Using Hive for persistent caching
link: httpLink,
),
);
Persistent caching improves offline capabilities and reduces data usage, making your app more efficient.
Automatic Code Generation
To avoid manually parsing GraphQL responses and ensure type safety, consider using code generation tools like graphql_codegen or artemis. These tools generate Dart models from your GraphQL schema, which you can use directly in your application.
Conclusion
The graphql_flutter package offers a comprehensive and efficient way to interact with GraphQL endpoints in your Flutter applications. With support for queries, mutations, subscriptions, and caching, it streamlines data fetching and manipulation while providing a great developer experience. By integrating GraphQL into your Flutter projects, you can build high-performance, data-driven applications that meet the demands of modern users.