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:
- Flutter version 2.2.x or later
- Android Studio or VS Code installed (with Plugins Dart and Flutter)
- An app created on Back4App.
- Note: Follow the New Parse App Tutorial to learn how to create a Parse App on Back4App.
- An Flutter app connected to Back4app.
- Note: Follow the Install Parse SDK on Flutter project to create an Flutter Project connected to Back4App.
- A device (or virtual device) running Android or iOS.
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
177
178
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 = 'YOUR_APP_ID_HERE';
final keyClientKey = 'YOUR_CLIENT_KEY_HERE';
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 todoController = TextEditingController();
void addToDo() async {
if (todoController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).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,
),
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)),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
onPrimary: Colors.white,
primary: Colors.blueAccent,
),
onPressed: addToDo,
child: Text("ADD")),
],
)),
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..."),
);
}
if (!snapshot.hasData) {
return Center(
child: Text("No Data..."),
);
} 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(() {
final snackBar = SnackBar(
content: Text("Todo deleted!"),
duration: Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..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:
- Make a new instance of the Parse Todo class with the command
ParseObject('Todo')
. - Use the
set
function to set the parameters for this object. - Call the
save
function, which will effectively register thetask
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 theParse Dashboard
or you can code thegetTodo
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 as List<ParseObject>;
} else {
return [];
}
To build this function, follow these steps:
- Create an instance of Parse’s Query class.
- Do a Query’s search method using
query()
method. - If the operations succeed, a list of Todo objects will be returned. If no object is found, the
success
property isfalse
, and theresults
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 as List<ParseObject>;
} 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 updateTodo
with:
1
2
3
4
var todo = ParseObject('Todo')
..objectId = id
..set('done', done);
await todo.save();
To build this function, follow these steps:
- Make a new instance of the Parse Todo class with the command
ParseObject('Todo')
. - Use the
objectId
property to set objectId of ParseObject that must be updated. - Use the
set
function to modify the parametersdone
your Task. - Call the
save
function, which will push the changes on thetask
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 changeddone
value in Task.
The screen will be reloaded and the task will have a new value for thedone
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:
- Make a new instance of the Parse Todo class with the command
ParseObject('Todo')
. - Use the
objectId
property to set objectId of ParseObject that must be removed. - Call the
delete
function, which will remove thetask
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.