Flutter

Download a Flutter GraphQL project with source code and start using Back4App

Introduction

In this tutorial, we are going to build an application that would parse data from Back4App backend through GraphQL. As you may know, GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. Back4App is a low code backend as a Service(based on Open Source Parse Platform) which is helping developers to build extensible and scalable mobile and web applications at a rapid pace.

Goals

The main goal is to build a simple app that is going to show a list of programming languages and their save type format.

At the end of this article we expect that you will be able to:

  • Make an API call on Back4App using GraphQL;
  • Get the data from GraphQL API;
  • Fetch data in your Flutter App.

Prerequisites

To complete this tutorial, you will need:

  • An IDE for writing Flutter code like Android Studio or Flutter. However we will be using Android Studio for this tutorial.
  • A Back4App account that can be created here: Back4App
  • GraphQL_flutter dependency.

Step 1: Clone project from Github to Android Studio

Go to the Github repo, and download the ZIP file, extract it, and open it in your flutter IDE.

To complete this step, drag the folder inside the zip file into your desktop, open Android Studio and then click on Open existing Android Studio Project. The project directory over would be usually ‘C:\Users\Username\Desktop\back4App_GraphQL-starting_code’.

But this may be different for different users.

Open the project and go to the lib\main.dart file. You will see so many errors there, don’t worry, just press all ‘Get dependencies’ and all the errors would perish. If doesn’t then just press ‘Upgrade dependencies’.

The code in your main.dart should look like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'consonents.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
      ),
    );
  }
}
class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('Parsing data using GraphQL',
          ),
        ),
        body: Container(),
    ),);
  }
}

If you open Run the app now you would only see an empty Screen in your emulator/device with only an AppBar titled ‘Parsing data using GraphQL’.

We will work on main.dart file and our important values are stored in consonents.dart and we can directly use it from there. We will require the graphql_flutter dependency to use graphQL in our app.

The graphql_flutter is most popular GraphQL cliet for flutter. It helps us to use GraphQL queries directly in our code. It provides us with GraphQLClient, GraphQLProvider and many other useful widgets that helps us to parse data from our database directly with the help of GraphQL without even using StreamBuilder. The package provides us with plenty of features including:

  • Subscriptions
  • In memory cache
  • Offline cache sync
  • Optimistic results
  • GraphQl file uploads

You can import it by writing the following code in pubspec.yaml file:

1
2
dependencies:
  graphql_flutter: ^3.1.0

See more about graphql_flutter at GraphQL Flutter Documentation

All dependencies are already pre-installed and you are good to proceed to the next step now.

Step 2 : Creating backend in Back4App

After you have signed up at Back4App website, u can proceed to the next step and create a new app. Click on ‘Build new App’. Give it the same name as your project name which over here is ‘back4app_Graphql’.

Now scroll down to Server settings on your left and select Settings Manage Parse Server Card, then select the option ‘3.10.0 - Parse Server 3.10.0’ from the list. Now press the save button below and wait until it gets saved.

Now come back to Core (on the left), select the API console, and select GraphQL Console from it.

This is the window where you can write and test your GraphQL queries/mutations code. Let’s proceed to the next step.

Step 3 : Creating and getting data through GraphQL

Now, let’s test the GraphQL API on Back4App using the GraphQL API Console. First, paste the following query on the left code-box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	mutation CreateClass {
		createClass(input:{
	    name: "Language"
	    schemaFields: {
	      addStrings: [{name: "name"}{name: "saveFormat"}]
	    }
	  }){
	    class{
	      schemaFields{
		name
		__typename
	      }
	    }
	  }
	}

The code above will create a class named “Language”. Let’s populate this new class with some rows:

1
2
3
4
5
6
7
8
	mutation CreateObject{
	  createLanguage(input:{fields: {name: "Python", saveFormat:".py"}}){
	    language{
	      name,
	      saveFormat
	    }
	  }
	}

If your operation is successful you’ll see this message on the right code-box on GraphQL PlayGround:

1
2
3
4
5
6
7
8
9
10
	{
	  "data": {
	    "createLanguage": {
	      "language": {
		"name": "Python",
		"saveFormat": ".py"
	      }
	    }
	  }
	}

Mutations are used to create or make changes on a class. By running the above mutation, we’ll create a new class named Language with data fields:

  • name: “Python”
  • saveFormat: “.py”

Repeat the process and create two more objects in the same Class for:

  • name: “C” and saveFormat: “.c”
  • name: “java” and saveFormat: “.java”.

The mutation for this will be such:

1
2
3
4
5
6
7
8
	mutation CreateObject{
	  createLanguage(input:{fields: {name: "C", saveFormat:".c"}}){
	    language{
	      name,
	      saveFormat
	    }
	  }
	}

Java:

1
2
3
4
5
6
7
8
	mutation CreateObject{
	  createLanguage(input:{fields: {name: "Java", saveFormat:".java"}}){
	    language{
	      name,
	      saveFormat
	    }
	  }
	}

Now let’s see all the data in our class Languages. For reading data we use query. So go ahead and type the below command.

1
2
3
4
5
6
7
8
9
10
11
	query FindLanguages{
	  languages{
	    count,
	    edges{
	      node{
		name,
		saveFormat
	      }
	    }
	  }
	}

In query FindLanguage ‘FindLanguage’ is just a name for your query command and you could even name it anything else. We use the find(className: "") command to find all the elements of the specific Class. count, returns the number of elements in the Class and all the elements are shown inside the result object.

The above query is going to return this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
	{
	  "data": {
	    "languages": {
	      "count": 3,
	      "edges": [
		{
		  "node": {
		    "name": "Python",
		    "saveFormat": ".py"
		  }
		},
		{
		  "node": {
		    "name": "C",
		    "saveFormat": ".c"
		  }
		},
		{
		  "node": {
		    "name": "Java",
		    "saveFormat": ".java"
		  }
		}
	      ]
	    }
	  }
	}

You can see all other queries in the following link:
GraphQL queries - Back4App

Now, let’s proceed to the next step.

Step 4 : Setting up GraphQL in our App

Let’s start coding our app. Before this, you must do few things in your lib\consonents.dart file:

  1. Copy graphql link which is next to the history button in the top of GraphQL window, and paste it in as kUrl string datatype.
  2. Now move to the bottom of the page and copy the codes from HTTP Headers, copy only the codes on the right of the colon(:) and paste them with their respective names in the lib\consonents.dart file:

The file should have following code:

1
2
3
4
5
	String kParseApplicationId= "APPLICATION_ID_COPIED_FROM_HEADERS";
	String kParseClientKey = "CLIENT_KEY_COPIED_FROM_HEADER";
	String kUrl= "URL_COPIED";
	// replace "APPLICATION_ID_COPIED_FROM_HEADERS", "CLIENT_KEY_COPIED_FROM_HEADER", "URL_COPIED" with real keys/ids copied 
	//from Http Headers tab.

Now move to main.dart file and head over to MyApp Stateless Widget and add the following code just above the return MaterialApp():

1
2
3
4
5
6
7
    final HttpLink httpLink = HttpLink(
      uri: kUrl,
      headers: {
        'X-Parse-Application-Id' : kParseApplicationId,
        'X-Parse-Client-Key' : kParseClientKey,    
      },
    );

HttpLink is from flutter_graphql.dart and takes the widget HttpLink() with two parameters.The first is the GraphQL API Url on Back4App. The second are the headers necessary to authenticate on Back4App API. After this section you need to include the GraphQL Client Code (explain what is), copy the code below and then paste under the HttpLink section:

1
2
3
4
5
6
	ValueNotifier<GraphQLClient> client = ValueNotifier(
	      GraphQLClient(
		cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject),
		link: httpLink,
	      ),
	    );

Now we’ve provided the link and cache method to our GraphQLClient. We have done this through our ValueNotifier and named it as client. Let’s wrap the MyHomePage() widget which is a child of MaterialApp with GraphQLProvider and pass MyHomePage() as its client. Add an another parameter inside GraphQLProvider namely client and pass client(name of our ValueNotifier) in it. This is how your MyApp class should look now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final HttpLink httpLink = HttpLink(
      uri: kUrl,
      headers: {
        'X-Parse-Application-Id' : kParseApplicationId,
        'X-Parse-Client-Key' : kParseClientKey,        
        //'X-Parse-REST-API-Key' : kParseRestApiKey,
      },//getheaders()
    );
    ValueNotifier<GraphQLClient> client = ValueNotifier(
      GraphQLClient(
        cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject),
        link: httpLink,
      ),
    );
    return MaterialApp(
      home: GraphQLProvider(
        child: MyHomePage(),
        client: client,
      ),
    );
  }
}

Let’s call the API and get the data

Step 5: Making an API call

Now we will work on the MyHomePageState. We’re going to start by initialising a String data type named ‘query’ and assign/pass the query statement for finding all the data from our Language class to it. Since the query is multi-line, we will pass the query in triple quotes. Here’s how it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
  String query='''
  query FindLanguages{
  languages{
    count,
    edges{
      node{
        name,
        saveFormat
      }
    }
  }
}
  ''';

Now go to Query() widget, inside the body parameter of the Scaffold which have two properties:

  • options
  • builder

And then pass null for both. This is how your build method would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('Parsing data using GraphQL',
          ),
        ),
          body: Query(
              options: null,
              builder: null
          ),
    ),);
  }

The Query() widget helps us write queries for GraphQL and will help us read and get data. We will pass the query statement which we have taken as a string in the options and build the widget with the builder parameter.

So pass the QueryOptions() widget in options as follows:

1
2
3
4
options: QueryOptions(
            documentNode: gql(query),
          ),

The query is passed through documentNode parameter in QueryOptions. Now let’s build with the help of builder Parameter. The builder method accepts a function with three parameters namely,

  • QueryResult result;
  • Refetch refetch;
  • FetchMore fetchmore.

Right now, we only need to worry about the QueryResult which gives us the result of our query and we can access the data through result.data .
So let’s code the following below:

1
2
3
4
5
6
7
8
 builder : (QueryResult result, { Refetch refetch,FetchMore fetchMore,})
          {
            if(result.data==null){
              return Center(child: Text("Loading...",style: TextStyle(fontSize: 20.0),));
            }else{
              return Text('SUCCESS');
            }
          },

In the above code we are accessing the data. If there’s no data we return a text widget reading ‘Loading…’ else we will return a Text() widget reading ‘SUCCESS’

Here’s how your myHomePage class in main.dart should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String name;
  String saveFormat;
  String objectId;

  String query = '''
  query FindLanguages{
  languages{
    count,
    edges{
      node{
        name,
        saveFormat
      }
    }
  }
}
  ''';

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text(
            'Parsing data using GraphQL',
          ),
        ),
        body: Query(
            options: QueryOptions(
              documentNode: gql(query),
            ),
            builder: (
                QueryResult result, {
                  Refetch refetch,
                  FetchMore fetchMore,
                }) {
              if (result.data == null) {
                return Center(
                  child: Text(
                    "Loading...",
                    style: TextStyle(fontSize: 20.0),
                  ),
                );
              } else{
                return Text('SUCCESS');
              }
            }
        ),
      ),
    );
  }
}

Now, start the App and wait for a few seconds after it restarts. If you see ‘SUCCESS’ on the screen then Congrats! You have established your connection and called the API.

Step 6: Getting and showing Data from API

Instead of returning the Text widget, we will return the ListView.builder() widget with original data. Write the following code instead of return Text('SUCCESS').

1
2
3
4
5
6
7
8
9
return ListView.builder(
                    itemBuilder: (BuildContext context, int index){
                      return ListTile(
                        title: Text(result.data["languages"]["edges"][index]["node"]['name']),
                        trailing: Text(result.data["languages"]["edges"][index]["node"]['saveFormat']),
                      );
                    },
                    itemCount: result.data["languages"]["edges"].length,
                  );

Now if you look back to your GraphQL result screen in your API console of Back4App where we typed our find method, the results were such:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "data": {
    "languages": {
      "count": 3,
      "edges": [
        {
          "node": {
            "name": "Python",
            "saveFormat": ".py"
          }
        },
        {
          "node": {
            "name": "C",
            "saveFormat": ".c"
          }
        },
        {
          "node": {
            "name": "Java",
            "saveFormat": ".java"
          }
        }
      ]
    }
  }
}

So from above code the location of “Python” was:

  • “data” -> “languages” -> “count” -> “edges” -> “node” -> “name”. Also notice name is inside square brackets ‘[]’ of edges which symbolizes it is the first element of edges list/array.

So we need to enter into this location to get “Python” and same for everything else. When we write result.data, we enter the “data” location. So to give the other locations we add ["location_name"] to it. So the location of “Python” will be result.data["languages"]["edges"][0]["node"]["name"].

When using ListView, it takes two parameters, itemCount, it tells the number of elements in the API call, itemBuilder, it takes a function with parameters (BuildContext context, int index) and returns a list widgets in which we will be showing the data. Here, we will use List of ListTile to show the data:

1
2
3
4
5
6
7
8
9
10
return ListView.builder(
                     itemCount: result.data["languages"]["edges"].length,
                    itemBuilder: (BuildContext context, int index){
                      return ListTile(
                        title: Text(result.data["languages"]["edges"][index]["node"]['name']),
                        trailing: Text(result.data["languages"]["edges"][index]["node"]['saveFormat']),
                      );
                    },
                    
                  );

When we replace Text('SUCCESS') with the above ListView.builder() widget, we first pass the itemCount where we pass the number of elements in the results List and so we don’t need to worry about that anymore. In itemBuilder we are returning a list of ListTiles which will have the "name" of the "Languages" class and in "saveFormat" in the trailing. Notice we used index instead of any number after result, this is what itemCount took care of.

This is how your main.dart should look like now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'consonents.dart';
import 'dart:ui';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final HttpLink httpLink = HttpLink(
      uri: 'https://parseapi.back4app.com/graphql',
      headers: {
        'X-Parse-Application-Id': kParseApplicationId,
        'X-Parse-Client-Key': kParseClientKey,        
      }, //getheaders()
    );

    ValueNotifier<GraphQLClient> client = ValueNotifier(
      GraphQLClient(
        cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject),
        link: httpLink,
      ),
    );

    return MaterialApp(
      home: GraphQLProvider(
        child: MyHomePage(),
        client: client,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String name;
  String saveFormat;
  String objectId;

  String query = '''
  query FindLanguages{
  languages{
    count,
    edges{
      node{
        name,
        saveFormat
      }
    }
  }
}
  ''';

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text(
            'Parsing data using GraphQL',
          ),
        ),
        body: Query(
          options: QueryOptions(
            documentNode: gql(query),
          ),
          builder: (
            QueryResult result, {
            Refetch refetch,
            FetchMore fetchMore,
          }) {
            if (result.data == null) {
              return Center(
                  child: Text(
                "Loading...",
                style: TextStyle(fontSize: 20.0),
              ));
            } else {
              return ListView.builder(
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(
                    title: Text(result.data["languages"]["edges"][index]["node"]
                        ['name']),
                    trailing: Text(result.data["languages"]["edges"][index]
                        ["node"]['saveFormat']),
                  );
                },
                itemCount: result.data["languages"]["edges"].length,
              );
            }
          },
        ),
      ),
    );
  }
}

And our final App Screen:

Conclusion

Now you have a Flutter App connected to a GraphQL API that can store and retrieve data on Back4App.

We did not require to encode or decode the json data separately which makes our work easier and faster using few lines of codes.

Have a great day !