Querying a GraphQL API in Flutter

GraphQL is gaining more popularity and this will probably only increase in the future. If you need some data storage, there is an increasing chance that you are going to connect to a GraphQL API. For this blog, we are going to assume this endpoint is already there. The endpoint we are going to use is the following. The endpoint returns information about the standing in Formula 1.

In the previous blog post, we showed how to draw a line chart for the current top three standings in Formula 1. We are now going to query the GraphQL API and draw the line chart for our own selection of drivers.

Setup

We are going to use the Graphql Flutter dependency. Installing new dependencies is easy in Flutter. We can simply edit the pubspec.yml. Here we will add the dependency!

dependencies:
  graphql_flutter: ^3.0.0

Don’t forget to run flutter pub get, before you continue!

Querying the API

This is the query we are going to use. The query will retrieve all race results for each race for each season (the API currently only retrieves the latest season). From the race result, we only need the driver’s name, the round of the race, and the number of points they scored. We sort the races by the round number and filter the results by the driver’s name.

query RaceResultsByRaceAndDriver {
  Season {
    Races {
      RaceResults(order_by: {Race: {round: asc}}, where: {Driver: {name: {_in: ["Verstappen","Hamilton","Bottas"]}}}) {
        Driver {
          name
        }
        Race {
          round
        }
        points
      }
    }
  }
}

Before we can use this query, we have to initialize the client. We do this in the wrapper around the MaterialApp. Here we provide a GraphQLClient with a link to the GraphQL API. Now we can use the Query widget in the LineChart Widget to get the data from the endpoint.

class LineChartDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final HttpLink link = HttpLink(
      uri: 'https://brief-quagga-80.hasura.app/v1/graphql',
    );

    ValueNotifier<GraphQLClient> client = ValueNotifier(
      GraphQLClient(
        cache: InMemoryCache(),
        link: link,
      ),
    );

    return GraphQLProvider(
      client: client,
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.green,
            title: Text("Graphql Query demo"),
          ),
          body: Padding(
            padding: const EdgeInsets.all(8.0),
            child: LineChart(),
          ),
        ),
      ),
    );
  }
}

To execute the query, we are going to define the query in the LineChart Widget. In the build method of the Widget, we can return a Query. We provide the query to the Options of the Query Widget to the documentNode. In the builder, we will get the results of the query when they are available. We have to check if the results are there. In a production app, it would also be wise to check if any errors occurred.

class LineChart extends StatelessWidget {
  static String fetchResults =
      """query RaceResultsByRaceAndDriver() {
  Season {
    Races {
      RaceResults(order_by: {Race: {round: asc}}, where: {Driver: {name: {_in:  ["Gasly", "Albon", "Verstappen", "Kvyat"]}}}) {
        Driver {
          name
        }
        Race {
          round
        }
        points
      }
    }
  }
}""";

  static List<String> drivers = ["Gasly", "Albon", "Verstappen", "Kvyat"];

  @override
  Widget build(BuildContext context) {
    return Query(
        options: QueryOptions(
          documentNode: gql(LineChart.fetchResults),
        ),
        builder: (QueryResult result,
            {VoidCallback refetch, FetchMore fetchMore}) {
          if (!result.loading) {
            List<LineData> data = convertToLineDate(result);
            return TweenAnimationBuilder(
                tween: Tween<double>(begin: 0, end: 100),
                duration: Duration(seconds: 8),
                builder:
                    (BuildContext context, double percentage, Widget child) {
                  return CustomPaint(
                    painter: LineChartPainter(
                        percentage, data, "Standings Formula One"),
                    child: Container(width: double.infinity, height: 340),
                  );
                });
          }
          return Text("Loading");
        });
  }
}

Finally we are going to map the QueryResult result to our LineData. Here we can map the Races to a dynamic list. Then for each race we check the results of the race and add them to the corresponding driver.

  List<LineData> convertToLineDate(QueryResult result) {
    Map<String, List<double>> driverPointsMap = new Map();
    drivers.forEach((driver) {
      driverPointsMap.putIfAbsent(driver, () => []);
    });
    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());
      });
    });
    final List<LineData> data = [];
    driverPointsMap.entries.forEach((element) {
      data.add(
          LineData(element.key, getDriverColor(element.key), element.value));
    });
    return data;
  }

Using variables in our query

Currently, we only retrieve the results for Verstappen, Hamilton, and Bottas. This way we get the exact same line chart like the one in the previous blog. We are going to make the drivers variable so that we can change the drivers when we want to see the results of other drivers. Luckily this is easy to do with the Graphql Flutter Package. First, we are going to change the query so that we can pass a variable:

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

To pass the variable to the query, the only thing we have to change is one of the options in the query:

options: QueryOptions(
  documentNode: gql(LineChart.fetchResults),
  variables: {
    'drivers': drivers,
  },
)

Now we can select the current Red Bull drivers and see how they compare against each other:

The full code can be found here on Github. One of the things that I disliked, coming from more typed bases languages (Kotlin, Java) was the way we had to convert the results of the query to the actual data we needed. Luckily this is also possible in Dart. In the next blog post, we will describe typing our model-based on the schema. Thank you for reading and do not hesitate to ask questions!

One comment

Leave a Reply