Flutter

Flutter CRUD Database Operations on Parse

Introduction

In this guide, you will learn how to use the Flutter plugin for Parse Server to manage Parse Objects at Back4App. Parse data storage works around the ParseObject, where each one contains key-value pairs of JSON-compatible data.

The Back4App Database supports the most common data types, including strings, numbers, booleans, DateTime, GeoPoints, Pointers, Relations, or even list and objects. In summary, it supports anything that can be JSON-encoded.

This guide contains the basic operations to manage objects. It’s a simple CRUD guide showing how to Create, Read, Update and Delete objects on Parse.
In four steps, you will be able to perform the four basic functions of persistent storage.

Prerequisites

To complete this tutorial, you will need:

Understanding our Todo App

To better understand Parse on Flutter, you will see the CRUD operations implemented on a ToDo App. The application will have a simple interface, with a text field to register a task and a list of registered tasks. You can update each task as completed or even delete the task.
We won’t explain the Flutter application code once this guide’s primary focus is using the Flutter with Parse.
Following the next steps, you will build a Todo App that will store the tasks at Back4App Database.

Let’s get started!

Following the next steps you will be able to build a Todo App that will storage the tasks at Back4App Database.

Step 1 - Create Todo App Template

Open your Flutter project from the previous guide Flutter plugin for Parse Server. Go to the main.dart file, clean up all the code, and replace it with:

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final keyApplicationId = 'xxxxxxxxx';
  final keyClientKey = 'xxxxxxxxxx';
  final keyParseServerUrl = 'https://parseapi.back4app.com';

  await Parse().initialize(keyApplicationId, keyParseServerUrl,
      clientKey: keyClientKey, debug: true);

  runApp(MaterialApp(
    home: Home(),
  ));
}

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final scaffoldKey = GlobalKey<ScaffoldState>();
  final todoController = TextEditingController();

  void addToDo() async {
    if (todoController.text.trim().isEmpty) {
      scaffoldKey.currentState.showSnackBar(SnackBar(
        content: Text("Empty title"),
        duration: Duration(seconds: 2),
      ));
      return;
    }
    await saveTodo(todoController.text);
    setState(() {
      todoController.clear();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Parse Todo List"),
        backgroundColor: Colors.blueAccent,
        centerTitle: true,
      ),
      key: scaffoldKey,
      body: Column(
        children: <Widget>[
          Container(
              padding: EdgeInsets.fromLTRB(17.0, 1.0, 7.0, 1.0),
              child: Row(
                children: <Widget>[
                  Expanded(
                    child: TextField(
                      autocorrect: true,
                      textCapitalization: TextCapitalization.sentences,
                      controller: todoController,
                      decoration: InputDecoration(
                          labelText: "New todo",
                          labelStyle: TextStyle(color: Colors.blueAccent)),
                    ),
                  ),
                  RaisedButton(
                      color: Colors.blueAccent,
                      textColor: Colors.white,
                      child: Text("ADD"),
                      onPressed: addToDo)
                ],
              )),
          Expanded(
              child: FutureBuilder<List<ParseObject>>(
                  future: getTodo(),
                  builder: (context, snapshot) {
                    switch (snapshot.connectionState) {
                      case ConnectionState.none:
                      case ConnectionState.waiting:
                        return Center(
                          child: Container(
                              width: 100,
                              height: 100,
                              child: CircularProgressIndicator()),
                        );
                      default:
                        if (snapshot.hasError) {
                          return Center(
                            child: Text("Error..."),
                          );
                        } else {
                          return ListView.builder(
                              padding: EdgeInsets.only(top: 10.0),
                              itemCount: snapshot.data.length,
                              itemBuilder: (context, index) {
                              
                                //*************************************
                                //Get Parse Object Values
                                final varTodo = snapshot.data[index];
                                final varTitle = '';
                                final varDone =  false;
                                //*************************************                                

                                return ListTile(
                                  title: Text(varTitle),
                                  leading: CircleAvatar(
                                    child: Icon(
                                        varDone ? Icons.check : Icons.error),
                                    backgroundColor:
                                        varDone ? Colors.green : Colors.blue,
                                    foregroundColor: Colors.white,
                                  ),
                                  trailing: Row(
                                    mainAxisSize: MainAxisSize.min,
                                    children: [
                                      Checkbox(
                                          value: varDone,
                                          onChanged: (value) async {
                                            await updateTodo(
                                                varTodo.objectId, value);
                                            setState(() {
                                              //Refresh UI
                                            });
                                          }),
                                      IconButton(
                                        icon: Icon(
                                          Icons.delete,
                                          color: Colors.blue,
                                        ),
                                        onPressed: () async {
                                          await deleteTodo(varTodo.objectId);
                                          setState(() {
                                            Scaffold.of(context)
                                                .removeCurrentSnackBar();
                                            final snackBar = SnackBar(
                                              content: Text("Todo deleted!"),
                                              duration: Duration(seconds: 2),
                                            );
                                            Scaffold.of(context)
                                                .showSnackBar(snackBar);
                                          });
                                        },
                                      )
                                    ],
                                  ),
                                );
                              });
                        }
                    }
                  }))
        ],
      ),
    );
  }

  Future<void> saveTodo(String title) async {
    await Future.delayed(Duration(seconds: 1), () {});
  }
  
  Future<List<ParseObject>> getTodo() async {
    await Future.delayed(Duration(seconds: 2), () {});
    return [];
  }

  Future<void> updateTodo(String id, bool done) async {
    await Future.delayed(Duration(seconds: 1), () {});
  }

  Future<void> deleteTodo(String id) async {
    await Future.delayed(Duration(seconds: 1), () {});
  }
}

Note:
When debug parameter in function Parse().initialize is true, allows displaying Parse API calls on the console. This configuration can assist in debugging the code. It is advisable to disable debug in the release version.

Step 2 - Connect Template to Back4app Project

Find your Application Id and Client Key credentials navigating to your app Dashboard at Back4App Website.

Update your code in main.dart with the values of your project’s ApplicationId and ClientKey in Back4app.

  • keyApplicationId = App Id
  • keyClientKey = Client Key

Run the project, and the app will load as shown in the image.

Step 3 - Code for Create Object

The create function will create a new Task with the title and done status equal to false.

Search function saveTodo in file main.dart.

Replace code inside saveTodo with:

1
2
    final todo = ParseObject('Todo')..set('title', title)..set('done', false);
    await todo.save();

To build this function, follow these steps:

  1. Make a new instance of the Parse Todo class with the command ParseObject('Todo').
  2. Use the set function to set the parameters for this object.
  3. Call the save function, which will effectively register the task to your database in the Parse Dashboard.

The complete code should look like this:

1
2
3
4
  Future<void> saveTodo(String title) async {
    final todo = ParseObject('Todo')..set('title', title)..set('done', false);
    await todo.save();
  }

To test it, click on the Run button in Android Studio/VSCode.
Enter a task title and click on the ADD button.
To confirm that the new object is in the database, you can access the Parse Dashboard or you can code the getTodo function.

Step 4 - Code for Read Object

The read function is responsible for querying the database and returning the list of objects.
Search function getTodo in file main.dart.
Replace code inside getTodo with:

1
2
3
4
5
6
7
8
9
    QueryBuilder<ParseObject> queryTodo =
        QueryBuilder<ParseObject>(ParseObject('Todo'));
    final ParseResponse apiResponse = await queryTodo.query();

    if (apiResponse.success && apiResponse.results != null) {
      return apiResponse.results;
    } else {
      return [];
    }

To build this function, follow these steps:

  1. Create an instance of Parse’s Query class.
  2. Do a Query’s search method using query() method.
  3. If the operations succeed, a list of Todo objects will be returned. If no object is found, the success property is false, and the results are null.

The complete code should look like this:

1
2
3
4
5
6
7
8
9
10
11
  Future<List<ParseObject>> getTodo() async {
    QueryBuilder<ParseObject> queryTodo =
        QueryBuilder<ParseObject>(ParseObject('Todo'));
    final ParseResponse apiResponse = await queryTodo.query();

    if (apiResponse.success && apiResponse.results != null) {
      return apiResponse.results;
    } else {
      return [];
    }
  }

Search the code below inside ListView.builder function:

1
2
3
4
5
6
	//*************************************
	//Get Parse Object Values
	final varTodo = snapshot.data[index];
	final varTitle = '';
	final varDone =  false;
	//*************************************                                

Replace code with:

1
2
3
4
5
6
	//*************************************
	//Get Parse Object Values
	final varTodo = snapshot.data[index];
	final varTitle = varTodo.get<String>('title');
	final varDone =  varTodo.get<bool>('done');
	//*************************************                                

Using this code, we can access the values of our ParseObject using the get method.
We use the get method to retrieve the title (string) and `done (bool).

To test it, click on the Run button in Android Studio/VSCode.
The list will display the previously registered Task.
Add new tasks, and the screen will be updated with the new Task on the list.

Step 5 - Code for Update Object

The update function is responsible for updating a task as completed or not.

Search function updateTodo in file main.dart.

Replace code inside getTodo with:

1
2
3
4
    var todo = ParseObject('Todo')
      ..objectId = id
      ..set('done', done);
    await todo.save();

To build this function, follow these steps:

  1. Make a new instance of the Parse Todo class with the command ParseObject('Todo').
  2. Use the objectId property to set objectId of ParseObject that must be updated.
  3. Use the set function to modify the parameters done your Task.
  4. Call the save function, which will push the changes on the task to your database in the Parse Dashboard.

The complete code should look like this:

1
2
3
4
5
6
  Future<void> updateTodo(String id, bool done) async {
    var todo = ParseObject('Todo')
      ..objectId = id
      ..set('done', done);
    await todo.save();
  }

To test it, click on the Run button in Android Studio/VSCode.
Choose a task, click on the checkbox for changed done value in Task.
The screen will be reloaded and the task will have a new value for the done field.

Step 6 - Code for Delete Object

The delete function is responsible for removed a task from database.
It is an irreversible action, which means that you should be careful while using it.

Search function deleteTodo in file main.dart.

Replace code inside deleteTodo with:

1
2
    var todo = ParseObject('Todo')..objectId = id;
    await todo.delete();

To build this function, follow these steps:

  1. Make a new instance of the Parse Todo class with the command ParseObject('Todo').
  2. Use the objectId property to set objectId of ParseObject that must be removed.
  3. Call the delete function, which will remove the task to your database in the Parse Dashboard.

The complete code should look like this:

1
2
3
4
  Future<void> deleteTodo(String id) async {
    var todo = ParseObject('Todo')..objectId = id;
    await todo.delete();
  }

To test it, click on the Run button in Android Studio/VSCode.
Choose a task, click on the Delete icon in Task.
The screen will be reloaded and the removed task will not be listed.

It’s done!

At this point, you have learned how to do the basic CRUD operations with Flutter on Parse.