Skip to main content

flutter_graphql_codegen 2.0.0 copy "flutter_graphql_codegen: ^2.0.0" to clipboard
flutter_graphql_codegen: ^2.0.0 copied to clipboard

A powerful code generator for Flutter GraphQL applications that generates type-safe Dart classes and client extensions from GraphQL schemas and operations.

flutter_graphql_codegen #

pub package license

A build_runner builder that reads your GraphQL schema and .graphql operation files and writes Dart code for package:graphql: model classes for the schema types and an extension on GraphQLClient for each operation.

You write this:

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

and call it like this:

final user = await client.getUserData(GetUserVariables(id: '42').toJson());
print(user?.name);

Installation #

dependencies:
  graphql: ^5.1.3
  json_annotation: ^4.8.1

dev_dependencies:
  flutter_graphql_codegen: ^2.0.0
  build_runner: ^2.4.6
  json_serializable: ^6.7.1

The generated model classes use json_serializable, so it has to be in your dev dependencies too.

Configuration #

Put a graphql_codegen.yaml next to your pubspec.yaml:

schema_url: lib/graphql/schema.graphql
output_dir: lib/graphql/generated
document_paths:
  - lib/graphql/documents/**/*.graphql

The builder looks for graphql_codegen.yaml by default. If you keep the file somewhere else, point to it from build.yaml:

targets:
  $default:
    builders:
      flutter_graphql_codegen|graphql_codegen:
        options:
          config_path: config/graphql_codegen.yaml

Where the schema comes from #

schema_url can be a path inside the project, an absolute path, a file:// URL or an HTTP(S) endpoint.

Local schema files are tracked by build_runner, so editing the schema triggers regeneration.

For an HTTP(S) endpoint the builder tries a plain GET (expecting SDL), then GET ?sdl, and finally a standard introspection query, which it converts to SDL. Each request times out after 30 seconds. The remote schema is downloaded once per build_runner run, so restart watch after the server's schema changes.

If the endpoint needs authentication, add headers. ${NAME} is replaced with the environment variable NAME, which keeps tokens out of the repository:

schema_url: https://api.example.com/graphql
schema_headers:
  Authorization: "Bearer ${GRAPHQL_TOKEN}"

All options #

Option Default Description
schema_url (or schema) required Where to load the schema from
document_paths (or documents) required Globs for operation files; also used to find shared fragments
output_dir lib/graphql/generated Where generated files go
strict_nullability false Follow ! from the schema exactly (see below)
file_naming snake_case snake_case, kebab-case, camelCase or PascalCase
emit_index true Write an index.dart that exports everything
scalar_mapping {} Map a custom scalar to an existing Dart type, e.g. Date: DateTime
schema_headers {} HTTP headers for downloading a remote schema
diagnostics_format plain plain or json error output

About strict_nullability: with the default false, list fields are always nullable, and the typed *Data methods return nullable values at every level. Turn it on for new projects. It's off by default only so existing code keeps compiling.

Running the generator #

dart run build_runner build --delete-conflicting-outputs

On a fresh checkout you need to run it twice. The first run writes types.dart and the operation files; the second lets json_serializable generate types.g.dart for the new classes. After that one run is enough unless the schema changes.

What you get #

output_dir ends up with:

  • types.dart, with a class for every object, input, interface and union in the schema, plus enums and JSON converters
  • one <operation>_graphql.g.dart per named operation (a file with three operations produces three files)
  • index.dart, which exports all of the above

For query GetUser($id: ID!), the operation file contains:

final getUserDocument = graphql.gql(r"""...""");      // the query, with fragments
const String getUserOperationName = 'GetUser';

class GetUserVariables { ... Map<String, dynamic> toJson() }

User? parseGetUserData(Map<String, dynamic>? data);   // response -> model

extension GetUserExtension on graphql.GraphQLClient {
  Future<QueryResult<Map<String, dynamic>>> getUser([Map<String, dynamic>? variables]);
  Future<User?> getUserData([Map<String, dynamic>? variables]);
}

Mutations work the same way through client.mutate. Subscriptions return streams:

client.userUpdatedData({'id': '42'}).listen((user) => print(user?.name));

If an operation selects more than one root field, *Data returns a small result class with one property per field (named after the alias if there is one):

// query Dashboard { me { id } notifications { id } }
final dashboard = await client.dashboardData();
print(dashboard?.me?.id);
print(dashboard?.notifications?.length);

The extension methods only cover the common case. For watchQuery, a specific fetchPolicy, or anything else package:graphql offers, use the document and the parser directly:

final query = client.watchQuery(WatchQueryOptions(
  document: getUserDocument,
  variables: GetUserVariables(id: '42').toJson(),
  fetchPolicy: FetchPolicy.cacheAndNetwork,
));
final users = query.stream.map((result) => parseGetUserData(result.data));

Variables #

GetUserVariables turns typed values into the map that package:graphql expects. Enums are sent by their schema name, DateTime as ISO-8601 in UTC, and input types through their toJson(). Optional variables you leave out are left out of the map entirely. If you need to send an explicit null, pass a map by hand.

Interfaces and unions #

These become abstract classes, and fromJson picks the concrete class by __typename. The builder adds __typename to every selection in the generated documents, so you don't have to. A type the generator didn't know about (say the server added a new union member) ends up as Unknown<Name> instead of crashing:

for (final result in await client.searchData({'term': 'dart'})) {
  switch (result) {
    case User user:
      print('user ${user.name}');
    case Post post:
      print('post ${post.title}');
    case UnknownSearchResult other:
      print('something new: ${other.typename}');
  }
}

Fragments #

Fragments can live in their own files, as long as those files match document_paths. Each operation document includes only the fragments it uses.

Names that aren't valid Dart #

A field called class or an enum value called default gets a trailing underscore in Dart (class_, Role.default_). JSON keys and values sent to the server keep the original GraphQL names.

Custom scalars #

GraphQL Dart
DateTime DateTime, written back as UTC ISO-8601
LocalDate DateTime, written back as yyyy-MM-dd
Decimal, Long small wrapper classes that keep the value as a string
Byte, Short Byte wrapper / int
anything else a generated wrapper class with raw (the JSON value as received) and value (its string form)

To use your own type instead, add it to scalar_mapping. The generator doesn't add imports, so stick to types that are always in scope (String, int, DateTime, Map<String, dynamic>, ...).

Non-nullable int, double and bool fields have lenient converters: a missing or malformed value becomes 0, 0.0 or false instead of throwing.

Errors #

Both getUser(...) and getUserData(...) throw OperationException when the response has GraphQL errors or the request failed, so you get the original error details:

try {
  final user = await client.getUserData({'id': '42'});
} on OperationException catch (e) {
  print(e.graphqlErrors);
  print(e.linkException);
}

Problems found during generation are reported with a code:

Code Meaning
CFG001 / CFG002 schema_url / document_paths missing from the config
CFG999 Other config problem (bad value, unset env variable in schema_headers)
SCH400 The schema doesn't parse
SCH404 Schema file not found
SCH500 Remote schema download failed; the message lists every attempt
BLD100 An operation file doesn't parse
BLD101 An operation uses a fragment that isn't defined anywhere
BLD102 Two fragments share a name but differ
BLD103 An operation selects a root field the schema doesn't have
BLD200 Writing index.dart failed

Known limitations #

The biggest one: the generated classes follow the schema types, not the fields your query selects. If User.name is String! in the schema and a query asks for user { id } only, parsing that User throws because name is missing. For now, select every non-nullable field of the object types you fetch. Per-query result types would fix this properly, but they'd change the generated API, so they're planned for a major release.

Other limitations:

  • Anonymous operations are skipped, with a warning. Give every operation a name.
  • If you rename an operation, the old generated file stays in output_dir. Delete it by hand.

Troubleshooting #

Target of URI hasn't been generated: types.g.dart or _$UserFromJson isn't defined: run build_runner build once more (see Running the generator).

Conflicting outputs: dart run build_runner clean, then build again.

Remote schema fails to download: read the SCH500 message. It says what each attempt (SDL, ?sdl, introspection) returned. A 401 or 403 usually means schema_headers are missing; HTML in the response usually means the URL points to a playground page instead of the API.

Development #

dart pub get
dart test             # includes a ~20s test that compiles and runs generated code
dart test -x compile  # skip that one

The compile test generates code for test/fixtures/schema.graphql, runs json_serializable and dart analyze on it, and executes it against a fake GraphQL link. If you change what the generator emits, that's the test that tells you whether the output still works.

Changes are listed in CHANGELOG.md. License: see LICENSE.

1
likes
0
points
323
downloads

Documentation

Documentation

Publisher

unverified uploader

Weekly Downloads

A powerful code generator for Flutter GraphQL applications that generates type-safe Dart classes and client extensions from GraphQL schemas and operations.

Homepage
Repository (GitHub)
View/report issues

Topics

#graphql #codegen #flutter #dart #type-safety

License

unknown (license)

Dependencies

build, glob, gql, graphql, http, json_annotation, path, yaml

More

Packages that depend on flutter_graphql_codegen