Introduction

Ever wonder how the big guys like Facebook, Twitter, Airbnb create robust frontend applications for their platforms? Well, one of the key ingredients in their success is the use of GraphQL.

But, what exactly is GraphQL?

GraphQL is a query language—surprisingly, that’s what the last two letters represent.

It’s a query language for your API. It enables the frontend app (a.k.a. client) to request the exact data they need from the server, whether for a specific page, component, or just whenever they need it.

GraphQL sample query

Technically speaking, it’s just a specificationnot an implementation. For you to be able to write and host GraphQL APIs, you need a server library that contains the implementation written for the platform of your choice—one of my favorites is the Apollo Server for NodeJS.

And for you to be able to consume these GraphQL APIs, you need to use one of the client-side implementations, such as the graphql_flutter.

You can find an exhaustive list of supported server and client libraries for GraphQL here.

Some benefits for frontend developers:

  • You only have to connect your app to a single endpoint
  • You don’t necessarily have to deal with multiple API/endpoint chainings yourself
  • Multiple caching mechanisms supported right out of the box

Most of the tedious work is likely to be handled in the backend—but hey, they also get extra time to rest because you don’t always have to ping them anymore whenever you need something changed ASAP.

Project

In this article, I will teach you how you can request or send data to a GraphQL API from a Flutter application.

Download the final project here.

Project Overview

Flutter application utilizing SpaceX GraphQL API

Setup the Flutter project

At the time of writing, I am using the Flutter SDK stable v1.17.5.

From your terminal, clone the repository:

https://github.com/joshuadeguzman/spacex-land-starter

In the pubspec.yaml, add the dependency graphql_flutter

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.3
  google_fonts: ^1.1.0
  intl: ^0.16.1
  cached_network_image: ^2.2.0+1
  graphql_flutter: ^3.0.0 # Add it here

Retrieve dependencies of the project

flutter packages get

Setup GraphQL client

In the main.dart file, add the following to your main() function

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  final HttpLink link = HttpLink(
    uri: 'https://api.spacex.land/graphql/',
  );

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

  runApp(MyApp(client: client));
}

As mentioned earlier, you only have to establish a connection to a single endpoint. In the project, you are only querying anonymously on this API—no authentication tokens or cookies are involved.

To finish the setup, you need to provide the GraphQL instance throughout the app by wrapping the MaterialApp with GraphQLProvider.

class MyApp extends StatelessWidget {
  final ValueNotifier<GraphQLClient> client;

  const MyApp({Key key, this.client}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;

    return GraphQLProvider(
      client: client,
      child: CacheProvider(
        child: MaterialApp(
          ...
          home: HomeScreen(),
        ),
      ),
    );
  }
}

Let’s get going and start querying the API.

Queries

In GraphQL, a query allows you to explicitly tell the API that you are only requesting data from the server. In a traditional RESTful API, this is equivalent to a GET method request.

Querying the launch history from the API

Querying the launch history from the API

Fetching the data from the server

Your first goal is to retrieve the users from the server, and display it on a list.

a. In the users.page.dart, import the graphql_flutter dependency

import 'package:graphql_flutter/graphql_flutter.dart

b. Remove the Center widget and uncomment the Query widget

class _UsersPageState extends State<UsersPage> {
  @override
  Widget build(BuildContext context) {
    // Remove this widget
    return Center();

    // Uncomment the entire [Query] widget
    return Query(
       ...
    );
  }
}

c. Add the users query

class _UsersPageState extends State<UsersPage> {
  final String _query = """
    query users {
      users {
        id
        name
        rocket
        twitter
      }
    }
  """;

  @override
  Widget build(BuildContext context) {
    ...
  }
}

As you can imagine, this is the exact query you are using in the GraphQL playground to get all the users from the server.

https://api.spacex.land/graphql/

https://api.spacex.land/graphql/

d. Next, add gql(_query) to the documentNode of QueryOptions

Query(
    options: QueryOptions(
      documentNode: gql(_query),
    ),
    builder: (
      QueryResult result, {
      VoidCallback refetch,
      FetchMore fetchMore,
    })
    ...
)

gql turns your String query into a standard GraphQL AST (a.k.a. Abstract Syntax Tree). To put it simply, Stephen Schneider once mentioned that AST is just a fancy way of saying heavily nested objects from his video.

e. Hit hot restart or press SHIFT + R in your terminal

Astronaut&rsquo;s landing page (pun intended)

Astronaut’s landing page (pun intended)

Everything else is self-descriptive, but what is happening here?

  1. After you finished querying the server, you parse the data using final List users = result.data["users"];
  2. Next, you retrieve each user from the list using final user = users[index]; inside a ListView.builder
  3. Lastly, you retrieve the data from the user, i.e. user['name'], and display it in a Text widget

For a more structured way on how to parse JSON objects, please read more about it here.

Optional: If for some reason you need to display the timestamp that indicates when the user was created, add timestamp in the String query.

final String _query = """
  query users {
    users {
      id
      name
      rocket
      twitter
      timestamp
    }
  }
""";

And add a widget to display the timestamp data, user['timestamp'], in the list.

Displaying timestamp in the list

Displaying timestamp in the list

Phew, that went easy.

I challenge you to do the same for the upcoming launches and launch history screens. I’ll leave you with the queries I used for the final project.

Upcoming launches query

/// lib/screens/home/launch_upcoming.page.dart

class LaunchUpcomingPage extends StatelessWidget {
  final DateFormat _dateFormat = DateFormat("MMMM dd, yyyy");

  final String _query = """
    query launchUpcoming {
        launchesUpcoming(limit: 10) {
          mission_name
          launch_date_utc
          rocket {
            rocket_name
            rocket_type
          }
          links {
            flickr_images
          }
        }
      }
  """;
}

Launch history query

/// lib/screens/home/launch_history.page.dart

class LaunchHistoryPage extends StatelessWidget {
  final DateFormat _dateFormat = DateFormat("MMMM dd, yyyy");

  final String _query = """
    query launchHistory {
        launchesPast(limit: 10) {
          mission_name
          launch_date_utc
          rocket {
            rocket_name
            rocket_type
          }
          links {
            flickr_images
          }
        }
      }
  """;
}

You might have noticed the limit parameter in the query, this is just simply to tell the server that you only need the first 10 items in the database. I’ll discuss GraphQL pagination on a separate article.

Mutations

A mutation is used when you want to modify a single or set of data on the server. You might have guessed it, using a mutation is the equivalent of using POST, PATCH, or PUT, DELETE methods in a traditional RESTful API.

Creating a new user

a. Inside the add_user.screen.dart, wrap the Form widget inside a Mutation widget

Wrapping Form in a Mutation widget

Simplest hack to wrap your Form inside a Mutation widget, this imports `graphql_flutter` as well

b. Add the following mutation to create a new user in the server

class _AddUserScreenState extends State<AddUserScreen> {
  ...

  String insertUser() {
    return """
      mutation insertUser(\$name: String!, \$rocket: String!, \$twitter: String!) {
        insert_users(objects: {
          name: \$name,
          rocket: \$rocket,
          twitter: \$twitter,
        }) {
          returning {
            id
            name
            twitter
            rocket
          }
        }
      }
    """;
  }

  ...
}

Notice how parameters are escaped in this query, these are examples of GraphQL variables.

c. Update the Mutation’s options and builder respectively

Mutation(
  options: MutationOptions(
    /// Insert mutation here
    documentNode: gql(insertUser()),

    /// Tell the GraphQL client to fetch the data from
    /// the network only and don't cache it
    fetchPolicy: FetchPolicy.noCache,

    /// Whenever the [Form] closes, this tells the previous [route]
    /// whether it needs to rebuild itself or not
    onCompleted: (data) => Navigator.pop(context, data != null),
  ),
  builder: (
    RunMutation runMutation,
    QueryResult result,
  ) {
    ...
  }
)

d. Look for the FlatButton, and add the following to its onPressed callback

if (_formKey.currentState.validate()) {
  ...

  runMutation({
    'name': _nameController.text,
    'rocket': _rocketNameController.text,
    'twitter': _twitterController.text,
  });
}

With runMutation, you can tell the GraphQL client when exactly do you need to modify a data in the server. You can call this function within the scope of the Mutation builder.

In addition, this is where you pass the data to the variables from the insertUser() mutation.

e. Hit hot restart or press SHIFT + R in your terminal

Adding new user

Adding @JeffBezos with his heavy-lift orbital launch vehicle

Awesome!

Now, I’ll challenge you again to add functionality to update the details of the users on the server.

Update user details query

class _UpdateUserScreenState extends State<UpdateUserScreen> {
/// lib/screens/update_user/update_user.screen.dart

  ...

  String _updateUser() {
    return """
    mutation updateUser(\$id: uuid!, \$name: String!, \$rocket: String!, \$twitter: String!) {
      update_users(_set: {name: \$name, rocket: \$rocket, twitter: \$twitter}, where: {id: {_eq: \$id}}) {
        returning {
          id
          name
          twitter
          rocket
        }
      }
    }
    """;
  }
}

Conclusion

GraphQL makes it easy for frontend developers to play around with the server-side data and just focus on building the features. I encourage you to watch the documentary on how Facebook engineers invented GraphQL to help solve their issues on building native apps for the platform.

Q: Is GraphQL going to replace traditional RESTful APIs? No. At least not in the near future. There are still some cases where RESTful API may be a better choice over GraphQL. And just like any other solutions in software development, there’s no silver bullet that will solve every business’ problems when it comes to serving APIs.

What’s next?

I have shared with you one of the simplest ways on how you can request and send data back to the server using graphql_flutter. But you’ll probably look for a better way of implementing this on your apps if scalability and maintainability are some of your major concerns.

For example, when working with large teams, you may need to have typesafe queries and mutations, auto-generated classes, tests and more. Well, I’ll show them to you in action next time. 👋

References