Flutter GraphQL: Quick start with Fauna

seanconnolly

Sean Connolly

Posted on May 7, 2021

Flutter GraphQL: Quick start with Fauna

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

We are working on a comprehensive online course for Flutter developers who want to build amazing Flutter apps with GraphQL.

Whether you have worked with Flutter and GraphQL before or are just getting started, there will be something in this course for you.

Learn more at GraphQL for Flutter Developers.

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!
}
Enter fullscreen mode Exit fullscreen mode

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.
  3. Enter flutter_spaceships for the name and select Save.
  4. In the left navigation, select 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.

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!
}
Enter fullscreen mode Exit fullscreen mode

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.

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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.

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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!]!
}
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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

Alt Text

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

flutter pub get
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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);

  @override
  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
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

Save the file in your /lib directory like so:

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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);

  @override
  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),
        );
      },
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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);

  @override
  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('🚀'),
        );
      },
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

Do you see the pattern yet?

query GetSpaceships {
  spaceships {
    data {
      _id
      name
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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.

💖 💪 🙅 🚩
seanconnolly
Sean Connolly

Posted on May 7, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related