Generating Models for a GraphQL Endpoint in Flutter

In the previous blog post, we showed how to query a GraphQL Endpoint in Flutter. One of the downsides from this approach was that it is easy to make mistakes when defining the query as a String in the application. Another problem is that there is no typing and if you added typing you would have to keep it in sync with the GraphQL Endpoint. In this blog post, we will show you how to generate models and queries with Artemis to solve those problems!

Setup of the project

Before we start, we have to add some dependencies to our build file.

dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: ^3.0.0
  json_serializable: ^3.0.0
  meta: '>=1.0.0 <2.0.0'
  gql: '>=0.7.3 <1.0.0'

dev_dependencies:
  artemis: ^5.1.0
  build_runner: ^1.5.0
  flutter_test:
    sdk: flutter

Generation of the models

If you are using Android Studio or IntelliJ you can add a .graphqlconfig file for your GraphQL endpoint. Otherwise, you can use a plugin to download the schema.graphql. The config file should point to the URL of the endpoint and have a reference to where the schema.graphql should be saved.

{
  "name": "Formula One GraphQL",
  "schemaPath": "schema.graphql",
  "extensions": {
    "endpoints": {
      "Default GraphQL Endpoint": {
        "url": "https://brief-quagga-80.hasura.app/v1/graphql",
        "headers": {
          "user-agent": "JS GraphQL"
        },
        "introspect": true
      }
    }
  }
}

This will download the schema.graphql which we will need to generate the models for our query. If this does not run, make sure you have the JS GraphQL IntelliJ Plugin installed. Now we have the schema we are going to copy the query we used in the previous blog and put it in a graphql folder at the root level of the project.

query RaceResultsByRaceAndDriver($drivers: [String!]) {
    Season {
        Races {
            RaceResults(order_by: {Race: {round: asc}}, where: {Driver: {name: {_in: $drivers}}}) {
                Driver {
                    name
                }
                points
            }
        }
    }
}

Now we can add the build.yaml to configure Artemis. We tell Artemis where to generate the code, where to find the schema.graphql and where to find our queries.

targets:
  $default:
    sources:
      - lib/**
      - graphql/**
      - schema.graphql
    builders:
      artemis:
        options:
          schema_mapping:
            - schema: schema.graphql
              queries_glob: graphql/*.graphql
              output: lib/graphql_api.dart

We can now start with the generation of the models with the following command:

pub run build_runner build

This should generate the models!

Using the models in the application

Now we can use the models in our app. First, let’s look at how we can adjust the query. Earlier we defined this as a String in the Widget. Now we can access the query that is generated.

  static String fetchResults =
      """query RaceResultsByRaceAndDriver(\$drivers: [String!]) {
  Season {
    Races {
      RaceResults(order_by: {Race: {round: asc}}, where: {Driver: {name: {_in: \$drivers}}}) {
        Driver {
          name
        }
        Race {
          round
        }
        points
      }
    }
  }
}""";

  @override
  Widget build(BuildContext context) {
    return Query(
        options: QueryOptions(
          documentNode: gql(LineChart.fetchResults),
          variables: {
            'drivers': drivers,
          },
        ),
    return Query(
        options: QueryOptions(
          documentNode: RaceResultsByRaceAndDriverQuery().document,
          variables: {
            'drivers': drivers,
          },
        ),

This is not the only of generating the models. We als have typing for the result of our query! We can use the factory method that comes with the models to cast the result to our model. To see the benefits let’s look at the before and after again:

    var races = result.data['Season'][0]['Races'] as List<dynamic>;
    races.forEach((race) {
      var results = race['RaceResults'] as List<dynamic>;
      results.forEach((raceResult) {
        var driver = raceResult['Driver']['name'] as String;
        var points = raceResult['points'] as int;
        driverPointsMap[driver].add(points.roundToDouble());
      });
    });
    RaceResultsByRaceAndDriver$query_root result =
      RaceResultsByRaceAndDriver$query_root.fromJson(queryResult.data);
    var races = result.Season.first.Races;
    races.forEach((race) {
      var results = race.RaceResults;
      results.forEach((raceResult) {
        var driver = raceResult.Driver.name;
        var points = raceResult.points;
        driverPointsMap[driver].add(points.roundToDouble());
      });
    });

As you can see we can cast the result with the fromJson method. After that, we can access all properties, without having to cast any of the values to strings or ints. This solves a lot of issues with typing and makes it easier to edit the queries in a GraphQL file, which reduces the risks of errors when defining your queries or mutations. If you want to check out the code, you can find it here! Thank you for reading and if you still have any questions, feel free to ask them.

Leave a Reply