Flutter

Store relational data using GraphQL

Introduction

In the last two tutorials we have performed GraphQL queries and mutations on a Back4App database from our Flutter App Project. We’re using GraphQL Flutter as our GraphQL client once this is a very robust and far used project. For those tutorials we’ve used a very simple data model with the most common types for our Classes.

Back4App is a flexible platform which allows you to create, store and query relational data. In this tutorial we’re going to give a dive deep into this capability by showing you how to use relations and pointers when saving and querying relational data on Back4App backend. Also, we are going to explore other data types that Back4App offers like: GeoPointer and Datetime.

Goals

At the end of this article you’ll be able to:

  • Create/update and delete Relational data (using Pointers and Relations)
  • Create/update GeoPointers
  • Create/update Date-Time.

Prerequisites

To complete this tutorial, you will need:

  • Make sure you have read the previous two guides - Start from template and GraphQL Mutation.
  • Download the project file from GitHub Repo which includes the previous code as well as the new GUI you would need.
  • Open the downloaded project on a Flutter IDE like VS Code or Android Studio.
  • Back4App account that can be created here.
  • Connect your tutorial to Back4App as per the previous tutorials. Start from template.

Step 1:Setting up Back-end

On our previous project our data model was very simple with just a single class: Language. Now we’re going to make it more complex by adding 2 new classes and relating them to Language.

Founder with format
Column Name   Description
name   Name of the founder of the language
Ownership with format
Column Name   Description
name   Name of owner company
date_owned   Date ownership gained by company
headquarters   Headquarters location of the company

We will create a one-to-one relation between class Language and class Founder using Parse Pointers, which will tell the founder of the specific language. Then, we create will one-to-many relation between class Language and class Ownership using Parse Relations that will tell which organisation/company owned the language, their Headquarters and the date they acquired the ownership of the language. The data model will look like that:

Before we move to our app let’s create the classes and data in our back-end that we would require. Go to your Back4App App and then go to the GraphQL Playground. Using the mutation below, create a class Founder where we will store the Language’s founders names.

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

Now let’s populate our class with some founder’s names. In the following steps we will use this data to create new languages pointing to Founder’s class. Use the mutation below on Back4App GraphQL Playground to create your founders.

1
2
3
4
5
6
7
8
mutation CreateObject{
  createFounder(input: {fields: {name: "James Gosling"}}){
    founder{
      id
      name
    }
  }
}

Here we have entered the JAVA programming language founder’s name. You can similarly for others too but right now this is enough for our guide.

Let’s create the Ownership class where we will store the language ownership, foundation date (Date-Time) and the Owner’s headquarter’s location (GeoPointer). Later we will create the relation between the Language class and Ownership. Proceed by running the code below for creating the class:

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

Now populate the Ownership class using the following mutations:

1
2
3
4
5
6
7
8
mutation CreateObject{
  createOwnership(input: {fields: {name: "Sun Microsystems"}}){
    ownership{
      id
      name
    }
  }
}


1
2
3
4
5
6
7
8
mutation CreateObject{
  createOwnership(input: {fields: {name: "Oracle"}}){
    ownership{
      id
      name
    }
  }
}

Now refresh the page, go to Ownership class on Database browser. Select the Add new Column from the top right. Select GeoPoint from the first drop down and name it headquarters in the second text field. And leave everything as it is and press the Add Column button.

Then go to your Language class on Database browser. Let’s add the relations to Ownership and Founder classes. Click over add new column and then choose the data type Pointer and the target class: Founder. Give the column the same name - founder - then press Add Column.

Now repeat that process by adding a new column called ownership using the data type Relation and selecting the class Ownership.

Now you have your data model ready to use on your Flutter App and to start saving and updating the data.

Step 2 : Creating/Adding and Deleting Pointers

Now you need to download the project boilerplate code from our GitHub repo open on your IDE. To connect your project to Back4App go to the GraphQL Playground and open then copy the Keys and the API URL as shown on the image below. Now paste them into the constants.dart and run your project.

Run the application in your Emulator. Got to the M floating Button on the bottom. That will take us to the page where we performed simple mutations. Here you will find an extra floating action button CM. That is hte button we’re going to use to perform our complex GraphQL mutations. Click on the CM button, and you will see another four buttons one for each operation we’re going to make on this guide.

Now open the databaseutils.dart file and scroll down to the addPointers() method. Here we will add the logic for adding pointers. We are going to point James Gosling as a Founder of Java Language. So first you will need to proceed to your Back4App backend and copy both Founder’s (James Gosling) and Language (Java) objectId.

Updating/Creating Pointer to Data

Proceed to our app inside the databaseutils.dart and inside the addPointers() method initialize a String addPointerQuery where we will assign the GraphQL query for adding Pointers as follows:

1
2
3
4
5
6
7
8
9
10
11
String addPointerQuery=
   '''
    mutation addPointer(\$languageId: ID!, \$founderId: UpdateLanguageFieldsInput! ){
      updateLanguage(input : {id : \$languageId, fields : \$founderId})
      {
        language{
          objectId
        }
      }
    }
   ''';

and initialize final variable to assign variables, notice we are taking the rowObjectId and pointersId as parameters where

  • rowObjectId - The object Id of row from where we will point
  • pointersId - The object Id to the row to be pointed.

So declare variable as:

1
2
3
4
5
6
7
8
final variable={
  "userId" : rowObjectId,
     "founderId": {
       "founder": {
         "link": pointersId
       }
     }
   };

Now like the last guide we will initialize the GraphQLClient and send query with the help of QueryOptions() and return its instance with QueryResults():

1
2
3
4
5
6
7
GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

   QueryResult queryResult = await client.query(
     QueryOptions(documentNode: gql(addPointerQuery), variables: variable),
   );
    return queryResult;

This is how your addPointers() method 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
Future<QueryResult> addPointers(String rowObjectId, String pointersId) async{
   print('addPointers');
   //code for add/update Pointers
   String addPointerQuery=
   '''
    mutation addPointer(\$languageId: ID!, \$founderId: UpdateLanguageFieldsInput! ){
      updateLanguage(input : {id : \$languageId, fields : \$founderId})
      {
        language{
          objectId
        }
      }
    }
   ''';
   final variable={
     "userId" : rowObjectId,
     "founderId": {
       "founder": {
         "link": pointersId
       }
     }
   };
   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

   QueryResult queryResult = await client.query(
     QueryOptions(documentNode: gql(addPointerQuery), variables: variable),
   );
    return queryResult;
 }

Hot Restart your app now, go to the CM button on the mutation page and then press the set Add Pointers button. Here enter the row objectId where the pointer needs to be added in the first Text Field and objectId of row where it would point in the second Text Field and then press Done. Now check your Dashboard and you must be able to see a relation under the founder column. You could click on it where it would take you to the row pointing to the Founder class.

Deleting Pointer to Data

Now proceed to the method deletePointers() where we will write logic to delete relations. To delete relations you just have to make minor changes to the query above, that is, in the variables just change the "link" with "remove". So after initializing final variable it would be:

1
2
3
4
5
6
7
8
final variable={
     "languageId" : rowObjectId,
     "founderId": {
       "founder": {
         "remove": pointersId
       }
     }
   };

Everything else will look exactly the same as the addPointers(). So your deletePointers() method looks 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
Future<QueryResult> deletePointers(String rowObjectId, String pointersId) async{
   print('deletePointers');
   //code for delete pointers
   String removePointersQuery=
   '''
    mutation addPointer(\$languageId: ID!, \$founderId: UpdateLanguageFieldsInput! ){
      updateLanguage(input : {id : \$languageId, fields : \$founderId})
      {
        language{
          objectId
        }
      }
    }
   ''';
   final variable={
     "languageId" : rowObjectId,
     "founderId": {
       "founder": {
         "remove": pointersId
       }
     }
   };
   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

   QueryResult queryResult = await client.query(
     QueryOptions(documentNode: gql(removePointersQuery), variables: variable),
   );
   return queryResult;

 }

You could now proceed to your Back4App dashboard and see that the *founder column of the specific row was deleted.

Step 3 : Creating/Adding and Deleting Date-time data

If you remember we have created a class Ownership ealier in Step 1 and stored some data into it. These are names of companies that owned Java Programing Language. So first let’s enter the dates the acuired the ownership.

Let’s proceed to databaseutils.dart and create or scroll down to addDateTime() function. Here we will write logic to add Data-Time datatype to our class. Initialize String addDateTimeQuery and assign it the query for creating date-time data:

1
2
3
4
5
6
7
8
9
10
11
String addDateTimeQuery=
   '''
   mutation addTime(\$rowId: ID!,\$dateOwned: Date!){
      updateOwnership(input : {id : \$rowId, fields : {date_owned : \$dateOwned}})
      {
        ownership{
          objectId
        }
      }
    } 
   ''';

And the final variable as:

1
2
3
4
final variable={
     "rowId": rowObjectId,
     "dateOwned": dateTime
   };

Now initialize the GraphQLClient and pass the query throught the QueryOption(). This is how your function would 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
Future<QueryResult> addDateTime(String rowObjectId, String dateTime) async{
   print('addDateTime');
   //code for add/update date-time
   String addDateTimeQuery=
   '''
   mutation addTime(\$rowId: ID!,\$dateOwned: Date!){
      updateOwnership(input : {id : \$rowId, fields : {date_owned : \$dateOwned}})
      {
        ownership{
          objectId
        }
      }
    } 
   ''';
   final variable={
     "rowId": rowObjectId,
     "dateOwned": dateTime
   };
   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();
   QueryResult queryResult = await client.query(
     QueryOptions(documentNode: gql(addDateTimeQuery), variables: variable),
   );
   return queryResult;
 }

Now Hot Restart application and note down the objectId of row containing Sun Microsystems in name column. Press the Add Date-time button. Now enter the objectId noted in the first text field and “02-24-1982” as MM-DD-YYYY format at the day when Java was pubished and press the Done button.

Proceed to your Back4App backend and you would date in form of 24 Feb 1982 at 00:00:00 UTC which means it has identified the datatype as Date-Time datatype. Similarly, you could add “01-27-2010” for the date_owned for Oracle.

Step 4: Adding/updating GeoPointer Data

Let’s add GeoPointer data to locate Head Quateres of of the comapnies from the Ownership table.
Proceed to database_utils.dart file and scroll down to addGeoPointers() function. Now initialize String addGeoPointers and assign the respective query:

1
2
3
4
5
6
7
8
9
10
11
String addGeoPointers=
    '''
    mutation addGeoPointer(\$objectId: ID!,\$latitude: Float!,\$longitude: Float!){
      updateOwnership(input : {id : \$objectId, fields : { headquarters:  {latitude : \$latitude, longitude : \$longitude}}})
      {
        ownership{
          objectId
        }
      }
    } 
    ''';

and final variable as:

1
2
3
4
5
final variable={
     "objectId": rowObjectId,
     "latitude": double.parse(latitude),
     "longitude":  double.parse(longitude),
   };

Since latitude and longitude are double values we need to parse them from String to double before sending it.Initialise the GrapQLClinet and pass query with by QueryOption(). This is how your addGeoPointers() function would 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
Future<QueryResult> addGeoPointers(String rowObjectId, String latitude, String longitude) async{
   print('add GeoPointers');
   //code for add/update Geopointers
   String addGeoPointers=
    '''
    mutation addGeoPointer(\$objectId: ID!,\$latitude: Float!,\$longitude: Float!){
      updateOwnership(input : {id : \$objectId, fields : { headquarters:  {latitude : \$latitude, longitude : \$longitude}}})
      {
        ownership{
          objectId
        }
      }
    } 
    ''';
   final variable={
     "objectId": rowObjectId,
     "latitude": double.parse(latitude),
     "longitude":  double.parse(longitude),
   };
   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();
   QueryResult queryResult = await client.query(
     QueryOptions(documentNode: gql(addGeoPointers), variables: variable),
   );
   return queryResult;
 }

Hot Restart your app and select the Add GeoPointers button. Enter 37.35 in the first text box and 121.95 in the second text box. Enter the objectId of the row with Sun Microsystems as name and press done. Now proceed to your Back4App Backend and you would see (37.35, 121.95) which are co-ordinates of the HeadQuaters and states that it identifies it as GeoPointers.

Step 5: Adding/Updating and Deleting Relation

In pointers we can nly point to one row, but with the help of relations we can make connection to multiple rows. So lets make relation of our Language table to these two rows for Ownership of the Java language.

Adding/Updating Relation

Proceed to database_utils.dart file and proceed to addRelation() function for writing the logic. Initialize String addRelationQuery and assign the query for relation as follows:

1
2
3
4
5
6
7
8
9
10
11
 String addRelationQuery=
     '''
     mutation addRelation(\$objectId: ID!, \$relationId: OwnershipRelationInput){
      updateLanguage(input : {id : \$objectId, fields : {ownership : \$relationId}})
        {
          language{
            objectId
          }
        }
      }
     ''';

and the final variable would be:

1
2
3
4
final variable= {
     "objectId": objectId,
     "relationId": relationId
   };

So after initializing the GraphQLClinet and passing the query like before this is how your database_utils.dart would 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
 Future<QueryResult> addRelation(String rowObjectId, String relationId) async{
   //code for add/update Relation.
   print('addRelation');
   String addRelationQuery=
     '''
     mutation addRelation(\$objectId: ID!, \$relationId: OwnershipRelationInput){
      updateLanguage(input : {id : \$objectId, fields : {ownership : \$relationId}})
        {
          language{
            objectId
          }
        }
      }
     ''';
   final variable= {
     "objectId": rowObjectId,
     "relationId": {
       "add": relationId
     }
   };
   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();
   QueryResult queryResult = await client.query(
     QueryOptions(documentNode: gql(addRelationQuery), variables: variable),
   );
   print(queryResult);
   return queryResult;
 }

SUCCESS your app has finally stored relational, datetime and GeoPointers data on Back4App!

Conclusion

In this guide we learned how to store relational data on Back4App using GraphQL form a Flutter App project. Also we worked with other GraphQL mutations like GeoPointers and Datetime, creating, updating and deleting data. On the next tutorial we are going to deep dive into queries to our flutter App.

This guide was written by Asim Junain, from HybrowLabs.