Joshua de Guzman
Posted on November 2, 2020
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.
Sample GraphQL query requesting for upcoming launch data
Technically speaking, it's just a specification---not 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.
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.
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.
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
Everything else is self-descriptive, but what is happening here?
- After you finished querying the server, you parse the data using
final List users = result.data["users"];
- Next, you retrieve each user from the list using
final user = users[index];
inside aListView.builder
- Lastly, you retrieve the data from the
user
, i.e.user['name']
, and display it in aText
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.
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
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
|
|
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
Posted on November 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.