Flutter

Flutter CRUD app example using GraphQL

Introduction

On our first Flutter-GraphQL guide, we’ve learned how to set up a Flutter project using GraphQL API to connect and query simple data on Back4App. Also, how we can use the Back4App GraphQL Playground to run queries/mutations to create/populate the App Database.

In this tutorial, we will add a feature with which we will directly create, update, and delete data from our Back4App backend directly from our App using GraphQL mutations.

Goals

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

  • Create data in our Backend using GraphQL API.
  • Update data from our Backend using GraphQL API.
  • Delete existing data from Backend using GraphQL API.

Prerequisites

To complete this tutorial, you will need:

NOTE: If you want a better understanding of the Back4App GraphQL API take a look at our GraphQL Cook Book and also check our GraphQL Schema on our API Playground.

Step 1:Setting up GUI

Create a new file mutation_page.dart. Here we will create our GUI for taking input from the user and perform Mutation tasks. Now paste the following code in mutation_page.dart.

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
import 'package:back4appgraphqldemo/app_ui.dart';
import 'package:back4appgraphqldemo/database_utils.dart';
import 'package:flutter/material.dart';

class MutationPage extends StatelessWidget {

  String langName,saveFormat,objectId;
  DatabaseUtils utils;

  @override
  Widget build(BuildContext context) {
    return AppUI(
      onChangedName: (text){
      langName=text;
      },
      onChangedSaveFormat: (text){
              saveFormat=text;
        },
      onChangedObjectId: (text){
      objectId=text;
      },
      sendDataButtonPressed: (){
      },
      deleteDataButtonPressed: (){
      },
      updateButtonPressed: (){
      },
    );
  }
}

AppUI() widget has already been made for you. So we have created a class named MutationPage and returnded the AppUI widget as its widget. We have also initialsed callback functions for our Text Fields such that Text in first textField will be stored in langName, second one in saveFormat and objectId from the last. Now proceed to main.dart and add a floatingActionButton parameter in Scaffold() widget of MyHomePage class and pass the following code into it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
floatingActionButton: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              heroTag: 'mutation_page',
              child: Text('M',
              style: TextStyle(
                color: Colors.white,
              ),
              ),
              onPressed: (){
                Navigator.pushReplacement(context, MaterialPageRoute(
                  builder: ((context){
                    return MutationPage();
                  })
                ));
              },
            ),
          ],
        ),

This will create a Floating Button that will Navigate us to the MutationPage() from our Home page. So now our GUI is set. You can now Hot Restart your App to see the GUI. This is how your 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
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import 'package:back4appgraphqldemo/mutation_page.dart';
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,
        'X-Parse-Master-Key': kParseMasterKey,
        //'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,
      ),
    );
  }
}

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',
          ),
        ),
        floatingActionButton: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              heroTag: 'mutation_page',
              child: Text('M',
              style: TextStyle(
                color: Colors.white,
              ),
              ),
              onPressed: (){
                Navigator.pushReplacement(context, MaterialPageRoute(
                  builder: ((context){
                    return MutationPage();
                  })
                ));
              },
            ),
          ],
        ),
        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,
              );
            }
          },
        ),
      ),
    );
  }
}

Step 2: Creating/adding data to the database

If you proceed to graphql_configration.dart you could see that we have already set our GraphQLclient and now we can use it anywhere. Let’s proceed to and create a file database_utils.dart and perform an operation for Creating data. Create a class DatabaseUtils{} and create a constructor that would receive the data parameters we would work on, here we will require langName,saveFormat, and objectId:

1
2
3
4
5
6
7
import 'package:back4appgraphqldemo/graphql_configration.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class DatabaseUtils{
  final String langName,saveFormat,objectId;
  DatabaseUtils({this.langName="",this.saveFormat="",this.objectId=""});
}

Create a functiion addData() which will be an asynchronous function to Create data and initialse our GraphQLClient by initialsing GraphQLConfigration class. Paste the following code into sendData() function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 Future<QueryResult> sendData() async{
     String addData='''
      mutation CreateObject(\$input: CreateLanguageFieldsInput){
        createLanguage(input: {fields: \$input}){
          language{
            name,
            saveFormat
          }
        }
      }
      ''';
      final variable ={
     "input":{
       "name" : langName,
       "saveFormat" : saveFormat,
     }
   };
      
    GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();
   
   }

Here we have initialized a variable addData and have passed the query to create data and initialized variable that will pass the query Variables. We also initialized the GraphQLClient that will help us to pass queries. We can pass the queries in the manner below:

1
2
3
4
QueryResult queryResult = await client.query(
     QueryOptions(documentNode: gql(addData), variables: variable),
   );
   return queryResult;

We used our GraphQLClient instance to write a query that accepts QueryOptions() that helps us to send a query as you have seen from the last tutorial. The result is stored in queryResult.
This is how your database_utils.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
import 'package:back4appgraphqldemo/graphql_configration.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class DatabaseUtils{
  final String langName,saveFormat,objectId;
  DatabaseUtils({this.langName="",this.saveFormat="",this.objectId=""});
  Future<QueryResult> sendData() async{
    String addData='''
      mutation CreateObject(\$input: CreateLanguageFieldsInput){
        createLanguage(input: {fields: \$input}){
          language{
            name,
            saveFormat
          }
        }
      }
      ''';
    final variable ={
      "input":{
        "name" : langName,
        "saveFormat" : saveFormat,
      }
    };

    GraphQlConfiguration configuration = GraphQlConfiguration();
    GraphQLClient client = configuration.clientToQuery();

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

Now proceed to your UI class mutation_page.dart. Lets code for send data button which can be coded inside the sendDataButtonPressed: parameter. Since we need the langName and saveFormat first check if it is not empty and then Create an instance of DatabaseUtils and the pass the langName and saveFormat parameter.

1
2
3
4
5
6
if(langName.isNotEmpty && saveFormat.isNotEmpty){
          utils = DatabaseUtils(
            langName: langName,
            saveFormat: saveFormat ,
          );
        }

After this call the sendData() function from the DatabaseUtils instance.

1
 utils.sendData();

Now you can Hot Restart app, fill the two text fields with their respective data and press the send data button. Now go back to your Query page by pressing the floating action button and you could see one more data is added to our table. This is how your MutationPage class 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
29
30
31
32
33
34
35
36
37
 import 'package:back4appgraphqldemo/app_ui.dart';
import 'package:back4appgraphqldemo/database_utils.dart';
import 'package:flutter/material.dart';

class MutationPage extends StatelessWidget {

  String langName,saveFormat,objectId;
  DatabaseUtils utils;

  @override
  Widget build(BuildContext context) {
    return AppUI(
      onChangedName: (text){
      langName=text;
      },
      onChangedSaveFormat: (text){
              saveFormat=text;
        },
      onChangedObjectId: (text){
      objectId=text;
      },
      sendDataButtonPressed: (){
      if(langName.isNotEmpty && saveFormat.isNotEmpty){
          utils = DatabaseUtils(
            langName: langName,
            saveFormat: saveFormat ,
          );
          utils.sendData();
        }
      },
      deleteDataButtonPressed: (){
      },
      updateButtonPressed: (){
      },
    );
  }
}

Step 3 : Updating Data

In DatabaseUtils create a Future function updateData(). Initialise a String update and pass in the update query and query variables in final variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 Future<QueryResult> updateData() async{
  String update='''
  mutation UpdateObject(\$id: ID!,\$input: UpdateLanguageFieldsInput){
    updateLanguage(input: {id:\$id, fields:\$input}){
      language{
        name,
        id
      }
    }
  }
  ''';
   final variable={
     "id":objectId,
     "input":{
       "name" : langName
     }
   };
 }

Now initialse our GraphQLClient and send the query through QueryOptions(). This is how your code 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> updateData() async{
 
   String update='''
    mutation UpdateObject(\$id: ID!,\$input: UpdateLanguageFieldsInput){
      updateLanguage(input: {id:\$id, fields:\$input}){
        language{
          name,
          id
        }
      }
    }
    ''';
 
   final variable={
     "id":objectId,
     "input":{
       "name" : langName
     }
   };

   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

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

Now come back to mutaion_page.dart and code in the updateButtonPressed: parameter. Check if langName, objectId and saveFormat are not empty and then call the updateData() funtion from DatabaseUtils class.

1
2
3
4
5
6
7
8
9
10
 updateButtonPressed: (){
        if(langName.isNotEmpty && saveFormat.isNotEmpty && objectId.isNotEmpty){
          utils = DatabaseUtils(
            langName: langName,
            saveFormat: saveFormat ,
            objectId: objectId
          );
          utils.updateData();
        }
      },

Go to Back4App Dashboard and choose a language to update, then copy its objectID. Hot restart your app and fill all the 3 text fields: the new name of the language you want to insert in first text field and the new save Format in second and objectId in third . Now press the Update data button and check the updated information on the App by clicking over the Q floating action button on bottom right.

Step 4: Deleting data

Create a deleteData() async function in DatabaseUtils and initialize String delete and pass the GraphQL query to delete data. Take final variables and pass the query variables into it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Future<QueryResult> deleteData() async{
    String delete='''
  mutation DeleteObject(\$id: ID!){
    deleteLanguage(input: {id:\$id}){
      language{
        name,
        id
      }
    }
  }
  ''';
   final variable={
     "id":objectId,
   };
 }

In this, we only require the objectId of the row that needs to be deleted. Initialize the GraphQLClient and send the query through QueryOptions().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Future<QueryResult> deleteData() async{
     String delete='''
  mutation DeleteObject(\$id: ID!){
    deleteLanguage(input: {id:\$id}){
      language{
        name,
        id
      }
    }
  }
  ''';
   final variable={
     "id":objectId,
   };

   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

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

   return queryResult;
 }

In MutationPage in the deleteDataButtonPressed: parameter check if objectId is not Empty or null and call the deleteData() function. Hot Restart application, enter the objectId of the row to be deleted and press the Delete Data button. It should delete a specific row from your Language class.

SUCCESS your app has finally done your mutation operations !!!

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
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import 'package:back4appgraphqldemo/mutation_page.dart';
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,
        'X-Parse-Master-Key': kParseMasterKey,
        //'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,
      ),
    );
  }
}

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',
          ),
        ),
        floatingActionButton: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              heroTag: 'mutation_page',
              child: Text('M',
              style: TextStyle(
                color: Colors.white,
              ),
              ),
              onPressed: (){
                Navigator.pushReplacement(context, MaterialPageRoute(
                  builder: ((context){
                    return MutationPage();
                  })
                ));
              },
            ),
          ],
        ),
        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,
              );
            }
          },
        ),
      ),
    );
  }
}

database_utils.dart should 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
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
import 'package:back4appgraphqldemo/graphql_configration.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class DatabaseUtils{

  final String langName,saveFormat,objectId;

  DatabaseUtils({this.langName="",this.saveFormat="",this.objectId=""});

  String delete='''
  mutation DELETE_LANGUAGES(\$id: ID!){
    deleteLanguage(input: {id:\$id}){
      language{
        name,
        id
      }
    }
  }
  ''';

  String addData='''
  mutation CREATE_LANGUAGES(\$input: CreateLanguageFieldsInput){
    createLanguage(input: {fields: \$input}){
      language{
        name,
        saveFormat
      }
    }
  }
  ''';
  String update='''
  mutation UPDATE_LANGUAGES(\$id: ID!,\$input: UpdateLanguageFieldsInput){
    updateLanguage(input: {id:\$id, fields:\$input}){
      language{
        name,
        id
      }
    }
  }
  ''';

 Future<QueryResult> sendData() async{

   final variable ={
     "input":{
       "name" : langName,
       "saveFormat" : saveFormat,
     }
   };
   print('sendingData');

    GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

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

 }
 Future<QueryResult> updateData() async{
   final variable={
     "id":objectId,
     "input":{
       "name" : langName
     }
   };

   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

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


 Future<QueryResult> deleteData() async{
   final variable={
     "id":objectId,
   };

   GraphQlConfiguration configuration = GraphQlConfiguration();
   GraphQLClient client = configuration.clientToQuery();

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

   return queryResult;
 }
}

mutaion_page.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import 'package:back4appgraphqldemo/app_ui.dart';
import 'package:back4appgraphqldemo/database_utils.dart';
import 'package:flutter/material.dart';

class MutationPage extends StatelessWidget {

  String langName,saveFormat,objectId;
  DatabaseUtils utils;

  @override
  Widget build(BuildContext context) {
    return AppUI(
      onChangedName: (text){
        langName=text;
      },
      onChangedSaveFormat: (text){
        saveFormat=text;
        },
      onChangedObjectId: (text){
        objectId=text;
      },

      sendDataButtonPressed: (){
        if(langName.isNotEmpty && saveFormat.isNotEmpty){
          utils = DatabaseUtils(
            langName: langName,
            saveFormat: saveFormat ,
          );
          utils.sendData();
        }
      },
      deleteDataButtonPressed: (){
        if(objectId.isNotEmpty){
          utils = DatabaseUtils(
            objectId: objectId,
          );
          utils.deleteData();
        }
      },
      updateButtonPressed: (){
        if(langName.isNotEmpty && saveFormat.isNotEmpty && objectId.isNotEmpty){
          utils = DatabaseUtils(
            langName: langName,
            saveFormat: saveFormat ,
            objectId: objectId
          );
          utils.updateData();
        }
      },
    );
  }
}

Conclusion

In this guide we used GraphQL mutations in the Flutter app to:

  • create new objects on Back4App;
  • update objects on Back4App;
  • delete objects on Back4App.

At this point you have a fully functional Flutter GraphQL CRUD project where you can use at starting point to develop your next App. In the next guide we are going to dive deep into queries showing how to make them to retrieve data from Back4App and show on our Flutter App.

Have a great day!