Flutter GraphQL: Quick start

Flutter is a UI toolkit for building mobile, web and desktop applications from a single codebase.

GraphQL is a powerful query language designed to make APIs fast, flexible, and developer-friendly.

Fauna makes it easy to create a GraphQL API backed by a scalable and secure transactional database.

In this series of articles, we’ll cover how to build a Flutter application powered by a Fauna GraphQL API.

Part 1: Quick start

In this article:

  • Why GraphQL with Flutter?
  • Why Fauna with Flutter?
  • Create a GraphQL API in minutes
  • Create data with a GraphQL mutation
  • Find a single item with a GraphQL query
  • Fetch multiple items with a GraphQL query
  • Set up a GraphQL client in Flutter
  • Populate a Flutter ListView with a GraphQL query

Why GraphQL with Flutter?#

I do not intend to write another GraphQL vs. REST comparison article, but it is beneficial to understand the benefits you should expect from using GraphQL in Flutter apps. Maintaining a clear understanding of what works well with GraphQL is essential to getting the most out of it.

Optimal data size#

When you fetch data with GraphQL, you specify exactly the structure you need for each query. If your screen only needs a few properties, you can greatly reduce bandwidth and latency to optimize the end user experience.

Data aggregation#

Many applications need to fetch many different types of resources to display useful information on a screen. For example, in a screen that displays a list of spaceships in your galactic starfleet, you may need to display information about each ship, the ship’s crew, the ship’s passengers, the planets where they are docked, etc. GraphQL allows you to specify what you need from each resource in a single query and takes care of the complexity of aggregation for you.

Intelligent caching#

In GraphQL, every resource is globally identifiable with a unique identifier and type classification. Client libraries can use this information to intelligently cache objects for later use. Two separate queries for the same data? No problem, the second query can be resolved straight from the cache without writing any additional code to manage that state.

Development flexibility#

Because clients specify the exact data needed in each query, frontend developers can easily add or remove properties from the query to fetch the data required. This means you don’t need to make changes to your backend API (or wait for somebody else to do it) if the display requirements of your screens change.

Why Fauna GraphQL with Flutter?#

Many concepts in this series will work for any GraphQL backend, but as you follow along, I hope you see the benefits of using Fauna with your Flutter apps.

Easy to get started#

You can create a globally distributed, failsafe, secure, and scalable GraphQL API up and running in just a few minutes.

Productive data modeling#

Fauna supports documents, relations, and graphs providing great data modeling flexibility that can be easily enhanced as your application evolves.

Zero infrastructure management#

Fauna provides an API, not just a database, so you don’t need to manage any infrastructure.

Power when you need it#

Fauna’s auto generated GraphQL API will help you get started. As your needs evolve, you can add custom queries and mutations with all the power of FQL, Fauna’s comprehensive query language.

Flutter + GraphQL Course
  • 45+ video modules
  • Lifetime access
  • Source code for all modules
  • All future updates
Starting learning Flutter + GraphQL now

Create a Fauna GraphQL API in minutes#

In the first section of this tutorial, we will create a new GraphQL API for Spaceships Flutter, an app that helps us manage a fleet of galactic spaceships.

Create a GraphQL schema definition file#

First, copy this text into a file called schema-definition.gql.

type Spaceship {
  name: String!
}

We refer to this file as a schema definition file because it provides the minimum set of information that allows Fauna to create collections, indexes, etc. and then make those available via GraphQL API.

The Spaceship type above defines our first resource type. For now, our spaceships only need a name, which is a string. The exclamation point on String! means this field is a required, non-nullable field.

Import the GraphQL schema definition file into Fauna#

  1. If you haven’t already, register an account with Fauna.
  2. From the Fauna dashboard, select New Database. New database
  3. Enter flutter_spaceships for the name and select Save. Enter database name
  4. In the left navigation, select GraphQL. Dashboard GraphQL
  5. Select Import Schema and upload the schema-definition.gql file we created earlier.
  6. When the upload is complete, you should land in the GraphQL Playground, which means you are ready to move on to the next step.

Explore the GraphQL API#

The GraphQL API is now available for us to start using, but what exactly did Fauna just do with that uploaded file?

Since we are here in the GraphQL Playground, let’s view the docs by selecting the Docs tab.

GraphQL Playground Docs

This view informs us of all of the operations available in the GraphQL API. The first section lists the available queries, operations that allow us to read data from our database. The next section lists mutations, operations that allow us to change the data in our database through creations, updates, and deletions.

These are all standard CRUD (Create - Read - Update - Delete) operations that Fauna implements for us based on the definition of our schema that we provided in the prior step.

All of this was generated by Fauna simply by defining the Spaceship type:

type Spaceship {
  name: String!
}

This single type definition generated 4 operations:

  • findSpaceshipById
  • createSpaceship
  • updateSpaceship
  • deleteSpaceship

Now that we’ve explored the GraphQL API, let’s start using it to build our space fleet.

Create data with GraphQL mutations#

Our fleet won’t be much of a fleet without some spaceships. Eventually we will be able to add spaceships from our Flutter app, but for now we will continue to use Fauna’s GraphQL Playground to write and execute mutations.

Since we are adding a spaceship, we need to create data. Let’s inspect the createSpaceship mutation.

GraphQL Playground Docs

We can see that the createSpaceship mutation accepts a single parameter called data, which is of type SpaceshipInput. SpaceshipInput includes a name property, which is the exact property we specified when we defined the Spaceship type in our original schema definition.

With that, we can enter this mutation into the input of the GraphQL Playground.

mutation {
  createSpaceship(data: { name: "Serenity" }) {
    _id
    _ts
    name
  }
}

Since the createSpaceship mutation returns a Spaceship, we can specify the information about the created spaceship that we want to retrieve after the mutation completes. _id and _ts are properties that Fauna adds to every type in its database; they capture the document’s unique identifier and creation timestamp respectively.

And as soon as we execute the mutation, we have a spaceship in our system.

GraphQL Playground Docs

Feel free to add a few more spaceships to your fleet. We will fetch and display these in a Flutter ListView later.

Find a single item with a GraphQL query#

Now that there is a spaceship in our system, how do we find it again later?

If we inspect the docs in the GraphQL Playground again, we can see the findSpaceshipById query is just what we need. We can also see that this query takes only one argument, the id of the spaceship we want to find.

GraphQL Playground Docs

In the prior mutation we requested the spaceship’s _id in the sub selection, so let’s copy that _id into the findSpaceshipById query to demonstrate how we can easily lookup a document with its unique identifier.

query {
  findSpaceshipByID(id: "enter-id-here") {
    _id
    name
  }
}

Replace the id value with the _id from the prior mutation response and execute the query in the GraphQL Playground. You should see a result like this: GraphQL Playground Docs

Fetch multiple items with a GraphQL query#

In our Spaceships Flutter app, we are not going to ask users to enter spaceship ids in order to view their fleet. Instead, we will want to display a list of spaceships in the fleet. Currently we can only get a single spaceship if we know its unique id, so how do we get a list of spaceships?

First we need to update our GraphQL schema by adding another type to the schema-definition.gql file we uploaded earlier.

type Spaceship {
  name: String!
}

type Query {
  spaceships: [Spaceship!]!
}

The Query type is a special type in GraphQL. It defines all of the top-level entry points that are available for clients to execute queries against. Here we are adding a spaceships query that will return a list of Spaceship items. The square brackets around [Spaceship!]! indicate this return type is a GraphQL array.

In the GraphQL Playground, there is an Update Schema action that will prompt you to upload the latest schema definition. Select schema-definition.gql again to update the schema.

GraphQL Playground Docs

After the upload is complete, inspect the docs again to see that a new spaceships query was added.

GraphQL Playground Docs

Notably, the final schema generated by Fauna is not exactly as it was defined. Instead of returning an array like we defined, the spaceships query returns a SpaceshipPage!. We can also see that the spaceships query accepts two inputs: _size and _cursor.

The return type and the additional inputs are here to allow us (well actually force us) to use pagination when fetching lists of items. Data sets in any system can grow very large and limiting the amount of data fetched in each query helps optimize server resources, latency, and bandwidth.

If we don’t specify a _size, Fauna will use its default limit of 64, which is fine for us for now since our fleet hasn’t exceeded that yet.

Go ahead and execute this query in the GraphQL Playground.

query {
  spaceships {
    data {
      _id
      name
    }
  }
}

GraphQL Playground Docs

Our GraphQL API is ready and has what we need to start building our Flutter app.

Set up a GraphQL client in Flutter#

In this section we are going to create a new Flutter project and use the Ferry GraphQL client to connect to our GraphQL API. There are a few other Flutter GraphQL clients available on pub.dev but I believe Ferry is the best option available and is easy to get started with.

If you haven’t worked with Flutter before, follow the Get started steps.

First, create a new Flutter project by running this command in your terminal:

flutter create flutter_spaceships

Next, we need to add a few dependencies to our project. This can be done by replacing pubspec.yaml with this:

name: flutter_spaceships
description: A new Flutter project.

publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=2.7.0 <3.0.0'

dependencies:
  flutter:
    sdk: flutter
  ferry: ^0.9.4
  ferry_flutter: ^0.4.0
  gql_http_link: ^0.3.2
  flutter_dotenv: ^3.1.0
  cupertino_icons: ^1.0.0

# This one is important to prevent build_runner errors
dependency_overrides:
  analyzer: ^0.40.4

dev_dependencies:
  flutter_test:
    sdk: flutter
  ferry_generator: ^0.3.3
  build_runner: ^1.10.1

flutter:
  uses-material-design: true
  assets:
    - .env

Your IDE may automatically download these packages. If not, you can also run:

flutter pub get

Most GraphQL APIs will require some kind of authorization token. We are not going to implement user authentication in this app, so for now we will use a single token and manage it with a .env file.

Note: Production apps should almost always implement user authentication. We are only skipping it for this tutorial because it takes extra work to set up that isn’t directly related to getting started with GraphQL.

First, you need to get a token from Fauna. From the Fauna dashboard, navigate to Security and then select New Key.

GraphQL Playground Docs

From here, we’ll select the Server role. You don’t need to specify a key name, but I called mine “development”.

Note: We are using the Server key for this tutorial but you would not typically want to include a Server key in your production app. You either want to implement proper user authentication or implement another role with limited access. I put together A Brief Guide on Public Roles in Fauna in case you need it.

After you save your key, copy it and paste it into a new file named .env in the root of your project like so:

# .env
FAUNA_KEY=your-key-goes-here

Note: If you commit this project to source control, be sure to add the .env to your .gitignore.

Next, open the main.dart file.

We won’t preserve any of the boilerplate in this file that was created when we first ran flutter pub create, so go ahead and delete all of that.

At the top of the file, let’s add a few import statements.

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart' as DotEnv;
import 'package:ferry/ferry.dart';
import 'package:gql_http_link/gql_http_link.dart';

The flutter_dotenv package will help us read the FAUNA_KEY environment variable we stored in our .env file.

Then we are importing Ferry, our GraphQL client, and then gql_http_link, which will allow us to access our GraphQL API over HTTP.

Next, we will instantiate the Ferry GraphQL client in the main function.

void main() async {
  await DotEnv.load();
  final client = Client(
    link: HttpLink("https://graphql.fauna.com/graphql", defaultHeaders: {
      'Authorization': 'Bearer ${DotEnv.env["FAUNA_KEY"]}',
    }),
  );
  runApp(App(client));
}

You can see that we only need to provide two things to interact with the GraphQL API: the endpoint and the Authorization header that includes the FAUNA_KEY we created earlier. Without the header, Fauna will not permit our app to access the API.

In a more complicated app, you would most likely use something like get_it or provider to make it easier to access the client. To keep this example simple and focused on working with GraphQL, we will simply pass it down to the App widget, which we will create next.

class App extends StatelessWidget {
  final Client _client;
  App(this._client);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Spaceships,
      theme: ThemeData(
        primarySwatch: Colors.indigo,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomeScreen(_client), // We will implement this later
    );
  }
}

This App is very similar to the default one created when we started the project. The main difference is that we added the _client as a constructor argument so that we can pass it to the HomeScreen, which is where we will execute a GraphQL query and display some data.

But before we can build the HomeScreen widget, we need to write our GraphQL query and make it executable in our Flutter app.

Write a GraphQL query in a Flutter app#

The home screen in our Flutter app will display a list of spaceships, answering the question:

What spaceships are in my fleet?

Thankfully, we already proved that we can answer this question when we were writing queries in Fauna’s GraphQL Playground.

Go ahead and copy this content into a file called spaceships.graphql (it should live in /lib as well).

# lib/spaceships.graphql
query GetSpaceships {
  spaceships {
    data {
      _id
      name
    }
  }
}

If you compare this query to the one we previously wrote, you will note one important difference. In this one, we added an operation name to our query: GetSpaceships. As we will see in a moment, the operation name is required for Ferry’s type generation.

Generate Dart classes from a GraphQL query#

One of the benefits of Ferry, and why I prefer it over some other GraphQL Flutter clients, is its support for code generation and fully typed data access. This allows us to confidently read and write data with the added convenience of IDE autocompletion.

The first step toward code generation with Ferry is adding our GraphQL schema to the project. To get this, we need to download a schema file from Fauna. Ferry will use this schema file to generate Dart classes from our data schema.

In the GraphQL Playground we were using before, just under the Docs tab is a Schema tab. Select Schema, then Download and then SDL to download the file.

GraphQL Playground Schema

Save the file in your /lib directory like so:

GraphQL Schema File

If you open schema.graphql, what you see is a reflection of what we see when we open the Docs tab in Fauna’s GraphQL playground. It’s the same information, just in a different format that can be read by Ferry to generate Dart classes.

Next, we need to create a build.yaml file at the root of our project and populate it with this content:

targets:
  $default:
    builders:
      gql_build|schema_builder:
        enabled: true
      gql_build|ast_builder:
        enabled: true
      gql_build|data_builder:
        enabled: true
        options:
          schema: flutter_spaceships|lib/schema.graphql
      gql_build|var_builder:
        enabled: true
        options:
          schema: flutter_spaceships|lib/schema.graphql
      gql_build|serializer_builder:
        enabled: true
        options:
          schema: flutter_spaceships|lib/schema.graphql

      ferry_generator|req_builder:
        enabled: true
        options:
          schema: flutter_spaceships|lib/schema.graphql

Note: If you used a different project name other than flutter_spaceships, be sure to replace the schema declarations with whatever you chose.

We just added a fair amount of config, but thankfully you don’t need to understand all of it to make use of it. The important thing is that we now have the correct pieces in place to generate our types by running a single command:

flutter pub run build_runner build --delete-conflicting-outputs

Here we are making use of the build_runner package that we included in our dependencies when we first created the Flutter project.

After that runs successfully, you should see several new files in your project that end with .gql.dart or .gql.g.dart. Notably, several of these are prefixed with spaceships and contain classes specifically generated from the spaceships.graphql query we previously added.

Now let’s see how we can make use of these classes and fetch some data in our Flutter app.

Populate a ListView with a GraphQL query#

Since our home screen will display a list of spaceships, the ListView widget is a good choice. But before we can populate our list, we need to import our newly generated classes.

# lib/main.dart
import 'package:ferry_flutter/ferry_flutter.dart';
import 'spaceships.req.gql.dart';
import 'spaceships.var.gql.dart';
import 'spaceships.data.gql.dart';

Ferry Flutter provides an Operation widget that makes it easier to work with GraphQL in Flutter apps. To use this widget, we will implement the HomeScreen widget like so:

class HomeScreen extends StatelessWidget {
  final Client _client;

  HomeScreen(this._client);

  
  Widget build(BuildContext context) {
    return Operation<GGetSpaceshipsData, GGetSpaceshipsVars>(
      client: _client,
      operationRequest: GGetSpaceshipsReq(),
      builder: (context, response, error) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Flutter Space Fleet"),
          ),
          body: response.loading
              ? Text('Loading...')
              : SpaceshipList(response.data.spaceships),
        );
      },
    );
  }
}

The Operation widget is making use of a few classes prefixed with GGetSpaceship. Earlier we mentioned the importance of the operation name in the GraphQL query we defined in spaceships.graphql and here we see how Ferry used it to generate unique classes just for that query.

By passing GGetSpaceshipsReq as the operationRequest, we are telling the Operation widget to execute our GraphQL query and then provide the results of the query to the widgets built inside the builder method. On the first build, the data will not be present yet as Ferry waits for a network response, so we display basic loading text.

After the network response is received, we can then render the data in the SpaceshipList, which is defined below.

class SpaceshipList extends StatelessWidget {
  final GGetSpaceshipsData_spaceships _spaceships;
  SpaceshipList(this._spaceships);

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _spaceships.data.length,
      itemBuilder: (context, i) {
        final spaceship = _spaceships.data[i];
        return ListTile(
          title: Text(spaceship.name),
          trailing: Text('🚀'),
        );
      },
    );
  }
}

GGetSpaceshipsData_spaceships may look like a funny name for a type, but it’s structure makes sense when you look again at the query that generated these types:

query GetSpaceships {
  spaceships {
    data {
      _id
      name
    }
  }
}

Since GraphQL queries can be nested, Ferry will generate new classes to capture these structures.

Since the data property under spaceships is a GraphQL array, it becomes a list in our Flutter app, meaning we can iterate, map filter, etc. just like any other list.

In our case, we are making use of ListView.builder, which can work with any list including our GraphQL generated list. If you inspect the class of the spaceship variable that we define in the itemBuilder, you can see another class generated by Ferry: GGetSpaceshipsData_spaceships_data.

Flutter GraphQL Widget

Do you see the pattern yet?

query GetSpaceships {
  spaceships {
    data {
      _id
      name
    }
  }
}

When we get to the leaf nodes of the query (the innermost nodes: _id and name) we find data in a format that we can display. The name field is a String and can be displayed for each spaceship in the list.

Flutter GraphQL App

Summary#

In the first part of this series, we created a GraphQL API in Fauna and were able to integrate it in a Flutter app.

With most of the setup out of the way, adding new widgets that also require data from the GraphQL API will only need a few steps:

  • Write a query in a .graphql file.
  • Build classes with build_runner.
  • Build a widget that uses the Operation widget and displays the data.

Find the full source code on GitHub: https://github.com/seanconnollydev/flutter_spaceships

In the next post of this series, we will explore how to add and edit spaceships with GraphQL mutations and see how the normalized cache keeps our application state up to date.