Chidume Nnamdi I'm a software engineer with over six years of experience. I've worked with different stacks, including WAMP, MERN, and MEAN. My language of choice is JavaScript; frameworks are Angular and Node.js.

Using GraphQL with Flutter: A tutorial with examples

7 min read 2125

GraphQL and Flutter Logos

Both GraphQL and Flutter introduced a new style of software development when they were first released. GraphQL enabled developers to fetch data in their desired shape and format. Flutter made it possible to build a mobile app in one language and cross-compile it for other platforms.

Combining these two revolutionary technologies opens yet another world of possibilities.

In this tutorial, we’ll demonstrate how to use GraphQL in a Flutter app, including how to make a query, make a mutation, and set up a subscription in a Flutter app using the graphql_flutter plugin. We’ll also show you how to consume GraphQL endpoints from a Flutter app.

Here’s what we’ll cover:

GraphQL and Flutter

Developed by Facebook in 2012 and released to the public in 2015, GraphQL was designed to revolutionize the way data is served from the backend. With GraphQL, you can state the structure of the data you want.

For example, let’s say we have the following table model in our database:

Food {
    name
    description
    price
    chef
    origin
}

We have a model for Food. The fields represent the properties of the food:

  • name property is the name of the food,
  • description describes the food in toto
  • price represents the selling price of the food
  • chef holds the name of the chef that cooked the food
  • origin states the history of the food

Using REST, we can fetch foods like this:

/GET
localhost:8080/foods
[
    {
        name: "Joolof Rice",
        desciption: "A spicy food",
        price: "$50",
        chef: "nnamdi",
        origin: "West Africa"
    }
]

As you can see, with REST, all the properties of each food are returned, whether we need them or not. We might only need to use the name and price propertied in our frontend, but all of the properties were returned.

GraphQL helps us avoid this redundancy. With GraphQL, we can state the properties we want to be returned, like so:

We made a custom demo for .
No really. Click here to check it out.

query foods {
    food {
        name
        price
    }
}

We’re telling the server that we only need the name and price properties from the Food table. It gives it to us exactly as we need:

{
  "data": [
    {
      "name": "Joolof Rice",
      "price": "$50"
    }
  ]
}

GraphQL is used by thousands of companies today. It has other benefits that make it a go-to choice, including:

  • Built-in caching and batching mechanism
  • Single endpoint structure (so you don’t have to deal with multiple API/endpoints)
  • Built-in pagination methods

GraphQL is a backend technology while Flutter is a frontend SDK that is used to build mobile apps. With mobile apps, we fetch the data displayed on the mobile app from a backend. Since GraphQL is a backend we can make our Flutter fetch data from a GraphQL backend.

It’s quite easy to build a Flutter app that fetches data from a GraphQL backend. You just need to make an HTTP request from the Flutter app, then use the returned data to set up the UI and display them.

The new graphql_flutter plugin provides APIs and widgets that enable you to fetch and use data from a GraphQL backend with ease.

What is graphql_flutter?

The new graphql_flutter plugin provides APIs and widgets that enable you to fetch and use data from a GraphQL backend with ease.

As the name implies, graphql_flutter is a GraphQL client for Flutter. It exports widgets and providers that can be used to fetch data from a GraphQL backend, including:

  • HttpLink — This is used to set the endpoint or URL of the backend
  • GraphQLClient — This class is used to fetch the query/mutation from a GraphQL endpoint and also to connect to a GraphQL server
  • GraphQLCache — This class is used to cache our queries and mutations. It has options store where we pass to it the type of store in its caching operation
  • GraphQLProvider — This widget wraps the whole graphql_flutter widgets so they can make queries/mutations. The GraphQL client to use is passed to this widget. This client is what this provider makes available to all widgets in its tree
  • Query — This widget is used to make a query to a GraphQL backend
  • Mutation — This widget is used to make a mutation to a GraphQL backend
  • Subscription — This widget is used to set up a subscription

Setting up graphql_flutter and GraphQLProvider

To use the graphql_flutter package, we have to create a Flutter project:

flutter create flutter_graphql
cd flutter_graphql

Next, install the graphql_flutter package:

flutter pub add graphql_flutter

The above code will install the graphql_flutter package. This will add the package graphql_flutter to the dependencies section of your pubspec.yaml file:

dependencies:
  graphql_flutter: ^5.0.0

To use the widgets, we have to import the package like this:

import 'package:graphql_flutter/graphql_flutter.dart';

First of all, before we begin making GraphQL queries and mutations, we have to wrap our root widget with GraphQLProvider. The GraphQLProvider must be provided a GraphQLClient instance to its client property.

GrpahQLProvider(
    client: GraphQLClient(...)
)

The GraphQLClient is provided with the GraphQL server URL and a caching mechanism.

final httpLink = HttpLink(uri: "http://10.0.2.2:4000/");

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

The URL of the GraphQL server is created using HttpLink. The instance of the HttpLink is passed to the GraphQLClient in a link property, which tells the GraphQLClient the URL of the GraphQL endpoint.

The cache passed to GraphQLClient tells it the cache mech to use. The InMemoryCache instance makes use of an in-memory database to persist or store caches.

The instance of the GraphQLClient is passed to a ValueNotifier. This ValueNotifer is used to hold a single value and has listeners that notify when the single value changes. graphql flutter uses this to notify its widgets when the data from a GraphQL endpoint changes, which helps keep graphql flutter reactive.

Now, we’ll wrap our MaterialApp widget with GraphQLProvider:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return GraphQLProvider(
        client: client,
        child: MaterialApp(
            title: 'GraphQL Demo',
            theme: ThemeData(
                primarySwatch: Colors.blue,
            ),
            home: MyHomePage(
                title: 'GraphQL Demo'
            ),
        )
    );
  }
}

Queries

To make a query using the graphql_flutter package, we’ll use the Query widget.

class MyHomePage extends StatelessWidget {
    @override
    Widget build(BuildContext) {
        return Query(
            options: QueryOptions(
                document: gql(readCounters),
                variables: {
                'counterId': 23,
                },
                pollInterval: Duration(seconds: 10),
            ),
            builder: (QueryResult result, { VoidCallback refetch, FetchMore fetchMore }) {
                if (result.hasException) {
                    return Text(result.exception.toString());
                }

                if (result.isLoading) {
                    return Text('Loading');
                }

                // it can be either Map or List
                List counters = result.data['counter'];

                return ListView.builder(
                itemCount: repositories.length,
                itemBuilder: (context, index) {
                    return Text(counters\[index\]['name']);
                });
            },
        )
    }
}

Here, the Query widget encloses the ListView widget where we will render the list of counters to fetch from our GraphQL server. So the Query widget must wrap the widget where you want to display the data fetched by the Query widget.

The Query widget must not be the top-most widget in a tree. It can be placed anywhere else provided the widget that will make use of its data is underneath or wrapped by it.

Also, the Query widget has two properties passed to it: options and builder.

options

options: QueryOptions(
    document: gql(readCounters),
    variables: {
    'conuterId': 23,
    },
    pollInterval: Duration(seconds: 10),
),

The option property is where the configuration of the query is passed to the Query widget. This options prop is an instance of the QueryOptions. The QueryOptions class exposes properties we use to set options for the Query widget.

The document property is used to set the query string or to pass in the query we want the Query widget to perform. Here, we passed in the readCounters string:

final String readCounters = """
    query readCounters(\$counterId: Int!) {
        counter {
            name
            id
        }
    }
""";

The variables property is where the query variables are sent to the Query widget. We have 'counterId': 23, there. This will be passed in place of $counterId in the readCounters query string.

The pollInterval is the time interval during which the Query widget will poll or refresh the query data. The time is set to 10 seconds, so after every 10 seconds, the Query widget will perform HTTP requests to refresh the query data.

builder

The builder property is a function. The function is called when the Query widget makes an HTTP request to the GraphQL server endpoint. The builder function is called by the Query widget with the data from the query, a function that is used to refetch the data, and a function that is used for pagination. This is used to fetch more data.

The builder function returns widgets below the Query widget. The result arg is an instance of the QueryResult. The QueryResult has properties that we can use to know the state of the query and the data returned by the Query widget.

  • QueryResult.hasException is set if the query encounters an error.
  • QueryResult.isLoading is set if the query is still in progress. We can use this property to display a UI progress to our users to tell them that something is on the way
  • QueryResult.data holds the data returned by the GraphQL endpoint

Mutations

Let’s see how to use the Mutation widget in graphql_flutter to make mutation queries.

The Mutation widget is used like this:

Mutation(
  options: MutationOptions(
    document: gql(addCounter),
    update: (GraphQLDataProxy cache, QueryResult result) {
      return cache;
    },
    onCompleted: (dynamic resultData) {
      print(resultData);
    },
  ),
  builder: (
    RunMutation runMutation,
    QueryResult result,
  ) {
    return FlatButton(
      onPressed: () => runMutation({
        'counterId': 21,
      }),
      child: Text('Add Counter')
    );
  },
);

Just like the Query widget, the Mutation widget takes some properties.

  • options is an instance of the MutationOptions class. This is where the mutation string and other configurations occur
  • document is used to set the mutation string. Here we have an addCounter mutation passed to the document. It will be run by the Mutation widget
  • update is called when we want to update the cache. The update function is called with the previous cache (cache) and the result of the mutation result. Anything returned from the update becomes the new value of the cache. Here, we are updating the cache based on the results
  • onCompleted is called when the mutations have been called on the GraphQL endpoint. Then the onCompleted function is called with the mutation result
  • builder is used to return the widget that will be under the Mutation widget tree. This function is called with a RunMutation instance, runMutation, and a QueryResult instance, result.
  • runMutation is used to run the mutation in this Mutation widget. Whenever it is called, the Mutattion widget triggers the mutation. This runMutation function is passed the mutation variables as parameters. Here, the runMutation is called with the counterId variable, 21

Now, when the mutation from the Mutation is complete, the builder is called so the Mutation rebuilds its tree. runMutation and the result of the mutation is passed to the builder function.

Subscriptions

Subscriptions in GraphQL are like an event system listening in on a WebSocket and calling a function when an event is emitted into the stream.

The WebSocket is opened from the client to the GraphQL server. Whenever the server emits an event from its end, the event is passed to the WebSocket. So this is real-time stuff.

In Flutter, the graphql_flutter plugin utilizes WebSockets and Dart stream to open and provide real-time updates from the server.

graphql_flutter has a Subscription widget we can use to open a real-time connection and comms to a GraphQL server.

Let’s see how we can use the Subscription widget to set up real-time connection in our Flutter app. First we define our subscription string:

final counterSubscription = '''
subscription counterAdded {
    counterAdded {
        name
        id
    }
}
''';

This subscription will give us a real-time update when a new counter is added to our GraphQL server.

Subscription(
    options: SubscriptionOptions(
        document: gql(counterSubscription),
    ),
    builder: (result) {
        if (result.hasException) {
            return Text("Error occured: " + result.exception.toString());
        }

        if (result.isLoading) {
            return Center(
                child: const CircularProgressIndicator(),
            );
        }

        return ResultAccumulator.appendUniqueEntries(
            latest: result.data,
            builder: (context, {results}) => ...
        );
    }
),

We see that the Subscription widget has many properties:

options holds the configuration for the Subscription widget document holds the subscription string builder returns the widget tree of the Subscription widget.

The builder function is called with the result of the subscription. The result has some useful properties:

result.hasException is set if the Subscription widget encounters an error polling for updates from the GraphQL server result.isLoading is set if the polling from the server is in progress

We have this ResultAccumulator.appendUniqueEntries() call. According to graphql_flutter’s pub.dev page, ResultAccumulator is a provided helper widget for collating subscription results.

Conclusion

We covered a lot in this Flutter and GraphQL tutorial. We started by introducing, in a nutshell, what GraphQL is and how it works. Then, we introduced graphql_flutter and demonstrated with examples how to make queries, mutations, and subscriptions from a Flutter app.

Monitor failed and slow GraphQL requests in production

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Chidume Nnamdi I'm a software engineer with over six years of experience. I've worked with different stacks, including WAMP, MERN, and MEAN. My language of choice is JavaScript; frameworks are Angular and Node.js.

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

Leave a Reply