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
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#
- If you haven’t already, register an account with Fauna.
- From the Fauna dashboard, select New Database.
- Enter flutter_spaceships for the name and select Save.
- In the left navigation, select GraphQL.
- Select Import Schema and upload the
schema-definition.gql
file we created earlier. - 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!
}
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
}
}
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
}
}
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!]!
}
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.
After the upload is complete, inspect the docs again to see that a new spaceships
query was added.
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
}
}
}
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.
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.
Save the file in your /lib
directory like so:
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
.
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.
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.
Looking for help with a development or design project?
Reach out to work with me or other senior-level talent.
Contact me