When building Flutter applications that interact with GraphQL APIs, handling errors gracefully is crucial for providing a smooth user experience. GraphQL errors can occur due to various reasons, such as invalid queries, server-side issues, or network problems. This article provides a comprehensive guide on effectively handling GraphQL errors in Flutter, complete with code samples to illustrate each technique.
Understanding GraphQL Errors
GraphQL responses include an errors
field, which is an array of error objects. Each error object contains details such as the error message, the location of the error in the query, and any extensions provided by the server.
{
"data": null,
"errors": [
{
"message": "Cannot query field 'nonExistentField' on type 'User'.",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"users",
0,
"nonExistentField"
]
}
]
}
Why is Error Handling Important?
- User Experience: Proper error handling prevents the app from crashing and provides informative messages to the user.
- Debugging: Detailed error messages assist developers in identifying and resolving issues quickly.
- Resilience: Handling network and server-side errors ensures the app remains functional even under adverse conditions.
Setting Up GraphQL Client in Flutter
First, set up a GraphQL client in your Flutter project. A popular choice is the graphql_flutter
package.
Step 1: Add Dependency
Add the graphql_flutter
package to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
graphql_flutter: ^5.1.0
Run flutter pub get
to install the package.
Step 2: Initialize GraphQL Client
Initialize the GraphQL client and wrap your app with GraphQLProvider
:
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(
link: httpLink,
cache: GraphQLCache(store: InMemoryStore()),
),
);
runApp(
GraphQLProvider(
client: client,
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GraphQL Error Handling',
home: Scaffold(
appBar: AppBar(
title: Text('GraphQL Error Handling'),
),
body: GraphQLQueryExample(),
),
);
}
}
Handling Errors with Query
Widget
The graphql_flutter
package provides a Query
widget that simplifies fetching data and handling responses. It automatically manages the loading, error, and data states.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class GraphQLQueryExample extends StatelessWidget {
final String getUsersQuery = """
query GetUsers {
users {
id
name
email
}
}
""";
@override
Widget build(BuildContext context) {
return Query(
options: QueryOptions(
document: gql(getUsersQuery),
),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Center(
child: Text('Error: ${result.exception.toString()}'),
);
}
if (result.isLoading) {
return Center(
child: 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']),
);
},
);
},
);
}
}
In this example:
Query
widget fetches data using the provided GraphQL query.- If
result.hasException
is true, an error message is displayed. - If
result.isLoading
is true, a loading indicator is shown. - If data is successfully fetched, it is displayed in a
ListView
.
Custom Error Handling
For more granular control over error handling, you can directly inspect the result.exception
property.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class CustomGraphQLQueryExample extends StatelessWidget {
final String getUsersQuery = """
query GetUsers {
users {
id
name
email
}
}
""";
@override
Widget build(BuildContext context) {
return Query(
options: QueryOptions(
document: gql(getUsersQuery),
),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('GraphQL Error Occurred!'),
SizedBox(height: 8),
ElevatedButton(
onPressed: () {
print('Error details: ${result.exception!.graphqlErrors}');
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Error Details'),
content: SingleChildScrollView(
child: ListBody(
children: result.exception!.graphqlErrors.map((e) => Text(e.message)).toList(),
),
),
actions: [
TextButton(
child: Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
child: Text('Show Error Details'),
),
],
),
);
}
if (result.isLoading) {
return Center(
child: 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']),
);
},
);
},
);
}
}
This example includes a button that displays detailed error messages from result.exception!.graphqlErrors
in a dialog. This approach helps provide more specific error information to the user.
Handling Network Errors
Network errors can occur due to connectivity issues. You can handle these by checking the linkException
property in the result.exception
.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class NetworkErrorGraphQLQueryExample extends StatelessWidget {
final String getUsersQuery = """
query GetUsers {
users {
id
name
email
}
}
""";
@override
Widget build(BuildContext context) {
return Query(
options: QueryOptions(
document: gql(getUsersQuery),
),
builder: (QueryResult result, {Refetch? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
if (result.exception!.linkException != null) {
return Center(
child: Text('Network Error: ${result.exception!.linkException.toString()}'),
);
} else {
return Center(
child: Text('GraphQL Error: ${result.exception!.graphqlErrors.toString()}'),
);
}
}
if (result.isLoading) {
return Center(
child: 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']),
);
},
);
},
);
}
}
In this code, network-related exceptions are checked, and a specific message is displayed to inform the user about connectivity issues.
Global Error Handling with ErrorLink
For more advanced error handling, you can use ErrorLink
from graphql_flutter
to handle errors at the link level. This is useful for logging errors or implementing retry mechanisms.
Step 1: Import Necessary Libraries
import 'package:graphql_flutter/graphql_flutter.dart';
Step 2: Implement ErrorLink
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 ErrorLink errorLink = ErrorLink(
onException: (request, response, exception) {
print('GraphQL Exception: $exception');
// Handle exception globally, e.g., log to analytics
},
onError: (request, response, context) {
print('GraphQL Error: ${response.errors}');
// Handle GraphQL errors globally
},
);
final Link link = errorLink.concat(httpLink);
final ValueNotifier client = ValueNotifier(
GraphQLClient(
link: link,
cache: GraphQLCache(store: InMemoryStore()),
),
);
runApp(
GraphQLProvider(
client: client,
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GraphQL Error Handling',
home: Scaffold(
appBar: AppBar(
title: Text('GraphQL Error Handling'),
),
body: GraphQLQueryExample(),
),
);
}
}
Here, ErrorLink
intercepts GraphQL exceptions and errors, allowing you to handle them globally. For instance, you can log errors to an analytics service or display a generic error message.
Conclusion
Handling GraphQL errors in Flutter is essential for creating robust and user-friendly applications. By using the Query
widget and implementing custom error handling strategies, you can provide detailed feedback to users and simplify the debugging process. Furthermore, leveraging ErrorLink
allows you to handle errors globally, ensuring consistent error management across your application.