Relationships on Android
Introduction
Using Parse, you can store data objects establishing relations between them. To model this behavior, any ParseObject
can be used as a value in other ParseObject
. Internally, the Parse framework will store the referred-to object in just one place, to maintain consistency. That can give you extra power when building and running complex queries. There are three main relation types:
one-to-one
, establishing direct relations between two objects and only them;one-to-many
, where one object can be related to many other objects;many-to-many
, which can create many complex relations between many objects.
There are two ways to create a one-to-many
relation in Parse:
- The first is using the
Pointers
inChild Class
, which is the fastest in creation and query time. - The second is using
Arrays
ofPointers
in Parent Class which can lead to slow query times depending on their size. Because of this performance issue, we will use only pointers examples.
There are three ways to create a many-to-many
relation in Parse.
- The first is using the Parse
Relations
, which is the fastest in creation and query time. We will use this in this guide. - The second is using
Arrays
ofPointers
which can lead to slow query times depending on their size. - The third is using
JoinTable
where the idea from classical database. When there is a many-to-nany relation, we combine everyobjectId
orPointer
from both sides together to build a new separate table in which the relationship is tracked.
This tutorial uses a basic app created in Android Studio 4.1.1 with buildToolsVersion=30.0.2
, Compile SDK Version = 30.0.2
and targetSdkVersion 30
At any time, you can access the complete Project via our GitHub repositories.
Goal
Our goal is, understand Parse Relations by creating a practical Book app.
Here is a preview of what we are gonna achieve:

Prerequisites
To complete this tutorial, we need:
- Android Studio.
- An app created on Back4App.
- Note: Follow the New Parse App tutorial to learn how to create a Parse App on Back4App.
- An android app connected to Back4App.
- Note: Follow the Install Parse SDK tutorial to create an Android Studio Project connected to Back4App.
- A device (or virtual device) running Android 4.1 (Jelly Bean) or newer.
Understanding the Book App
The main object class you’ll be using is the Book
class, storing each book entry in the registration. Also, these are the other three object classes:
Publisher
: book publisher name,one-to-many
relation withBook
;Genre
: book genre,one-to-many
relation withBook
. Note that for this example we will consider that a book can only have one genre;Author
: book author,many-to-many
relation withBook
, since a book can have more than one author and an author can have more than one book as well;
A visual representation of these data model:

Let’s get started!
Before next steps, we need to connect
Back4App
to our application. You should save theappId
andclientKey
from theBack4App
tostring.xml
file and then initParse
in ourApp.java
orApp.kt
file.
Follow the New Parse App tutorial if you don’t know how to initParse
to your app.
Or you can download the projects we shared the github links above and edit only theappId
andclientKey
parts according to you.
Step 1 - Save and list related objects of books
In this step we will see how to save and list the Genres
, Publishers
and Authors
classes related with the Book
class.
Step 1.1 - Save and list Genres
We can register a Genre
using the following snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void addGenre(String name) {
//We are taking this name parameter from the input.
progressDialog.show();
ParseObject parseObject = new ParseObject("Genre");
parseObject.put("name", name);
parseObject.saveInBackground(e -> {
progressDialog.dismiss();
if (e == null) {
getGenres();
inputGenre.setText("");
Toast.makeText(this, "Genre saved successfully", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private fun addGenre(name: String) {
//We are taking this name parameter from the input.
progressDialog.show()
val parseObject = ParseObject("Genre")
parseObject.put("name", name)
parseObject.saveInBackground {
progressDialog.dismiss()
if (it == null) {
getGenres()
inputGenre.setText("")
Toast.makeText(this, "Genre saved successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, it.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
}
We can list Genre
s using the following snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void getGenres() {
progressDialog.show();
ParseQuery<ParseObject> query = new ParseQuery<>("Genre");
query.findInBackground((objects, e) -> {
progressDialog.dismiss();
List<ParseObjectModel> list = new ArrayList<>();
for (ParseObject parseObject : objects) {
list.add(new ParseObjectModel(parseObject));
}
GenreAdapter adapter = new GenreAdapter(list, this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private fun getGenres() {
progressDialog.show()
val query = ParseQuery<ParseObject>("Genre")
query.findInBackground { objects, e ->
progressDialog.dismiss()
var list: MutableList<ParseObjectModel> = ArrayList()
for (parseObject in objects) {
list.add(ParseObjectModel(parseObject))
}
val adapter = GenreAdapter(this, list)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
}
}

Step 1.2 - Save and list Publishers
We can register a Publisher
using the following snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void addPublisher(String name) {
//We are taking this name parameter from the input.
progressDialog.show();
ParseObject parseObject = new ParseObject("Publisher");
parseObject.put("name", name);
parseObject.saveInBackground(e -> {
progressDialog.dismiss();
if (e == null) {
getPublishers();
inputPublisher.setText("");
Toast.makeText(this, "Publisher saved successfully", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private fun addPublisher(name: String) {
//We are taking this name parameter from the input.
progressDialog.show()
val parseObject = ParseObject("Publisher")
parseObject.put("name", name)
parseObject.saveInBackground {
progressDialog.dismiss()
if (it == null) {
getPublishers()
inputPublisher.setText("")
Toast.makeText(this, "Publisher saved successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, it.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
}
We can list Publisher
s using the following snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void getPublishers() {
progressDialog.show();
ParseQuery<ParseObject> query = new ParseQuery<>("Publisher");
query.findInBackground((objects, e) -> {
progressDialog.dismiss();
List<ParseObjectModel> list = new ArrayList<>();
for (ParseObject parseObject : objects) {
list.add(new ParseObjectModel(parseObject));
}
PublisherAdapter adapter = new PublisherAdapter(list, this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private fun getPublishers() {
progressDialog.show()
val query = ParseQuery<ParseObject>("Publisher")
query.findInBackground { objects, e ->
progressDialog.dismiss()
val list: ArrayList<ParseObjectModel> = ArrayList()
for (parseObject in objects) {
list.add(ParseObjectModel(parseObject))
}
val adapter = PublisherAdapter(this, list)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
}
}

Step 1.3 - Save and list Authors
We can register an Author
using the following snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void addAuthor(String name){
//We are taking this name parameter from the input.
progressDialog.show();
ParseObject parseObject = new ParseObject("Author");
parseObject.put("name", name);
parseObject.saveInBackground(e -> {
progressDialog.dismiss();
if (e == null) {
getAuthors();
inputAuthor.setText("");
Toast.makeText(this, "Author saved successfully", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private fun addAuthor(name: String) {
//We are taking this name parameter from the input.
progressDialog.show()
val parseObject = ParseObject("Author")
parseObject.put("name", name)
parseObject.saveInBackground {
progressDialog.dismiss()
if (it == null) {
getAuthors()
inputAuthor.setText("")
Toast.makeText(this, "Author saved successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, it.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
}
We can list Author
s using the following snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void getAuthors() {
progressDialog.show();
ParseQuery<ParseObject> query = new ParseQuery<>("Author");
query.findInBackground((objects, e) -> {
progressDialog.dismiss();
List<ParseObjectModel> list = new ArrayList<>();
for (ParseObject parseObject : objects) {
list.add(new ParseObjectModel(parseObject));
}
AuthorAdapter adapter = new AuthorAdapter(list, this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private fun getAuthors() {
progressDialog.show()
val query = ParseQuery<ParseObject>("Author")
query.findInBackground { objects: List<ParseObject>, e: ParseException? ->
progressDialog.dismiss()
val list: ArrayList<ParseObjectModel> = ArrayList()
for (parseObject in objects) {
list.add(ParseObjectModel(parseObject))
}
val adapter = AuthorAdapter(this, list)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
}
}

In this part, we use a model class named ParseObjectModel
. In this model class, we have a ParseObject
variable to be able to read the data, and the isChecked
variable, which we will use to save the book in the next step. We will be able to easily retrieve the selected objects with the isChecked
variable.
Here is the our ParseObjectModel
model.
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
public class ParseObjectModel {
ParseObject object;
boolean isChecked = false;
public ParseObjectModel(ParseObject object) {
this.object = object;
}
public ParseObject getObject() {
return object;
}
public ParseObjectModel setObject(ParseObject object) {
this.object = object;
return this;
}
public boolean isChecked() {
return isChecked;
}
public ParseObjectModel setChecked(boolean checked) {
isChecked = checked;
return this;
}
}
1
2
3
4
5
6
7
8
class ParseObjectModel(obj: ParseObject) {
var obj: ParseObject? = null
var isChecked: Boolean = false
init {
this.obj = obj
}
}
Step 2 - Save a Book object and its relations
Step 2.1 - Save a Book object with 1:N Relationship
This function will create a new Book
in Back4app database with 1:N relations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
progressDialog.show();
book.put("genre", genre);
book.put("publisher", publisher);
book.put("title", title);
book.put("year", year);
book.saveInBackground(e -> {
progressDialog.hide();
if (e == null) {
Toast.makeText(AddBookActivity.this, "Book saved successfully", Toast.LENGTH_SHORT).show();
startActivity(new Intent(AddBookActivity.this, BookListActivity.class));
finish();
} else {
Toast.makeText(AddBookActivity.this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
progressDialog.show()
book.put("genre", genre)
book.put("publisher", publisher!!)
book.put("title", title)
book.put("year", year)
book.saveInBackground {
progressDialog.hide()
if (it == null) {
Toast.makeText(this, "Book saved successfully", Toast.LENGTH_SHORT).show()
startActivity(Intent(this@AddBookActivity, BookListActivity::class.java))
finish()
} else {
Toast.makeText(this, it.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
Step 2.2 Save a Book object with N:N Relationship
This function will create a new Book
in Back4app database with N:N relations. For the Author
relation, we find the selected Author/s in the adapter of the authorRecyclerView
and save them as Parse Relation
.
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
progressDialog.show();
book.put("genre", genre);
book.put("publisher", publisher);
book.put("title", title);
book.put("year", year);
//Here we are setting book relation with getSelectedItem function of BookAuthorAdapter.
if (recyclerViewAuthors.getAdapter() != null) {
relation = ((BookAuthorAdapter) recyclerViewAuthors.getAdapter()).getSelectedItems(book);
if (relation == null) {
Toast.makeText(this, "Please select Author/s", Toast.LENGTH_SHORT).show();
return;
}
} else {
Toast.makeText(this, "Something went wrong!!", Toast.LENGTH_SHORT).show();
return;
}
book.saveInBackground(e -> {
progressDialog.hide();
if (e == null) {
Toast.makeText(AddBookActivity.this, "Book saved successfully", Toast.LENGTH_SHORT).show();
startActivity(new Intent(AddBookActivity.this, BookListActivity.class));
finish();
} else {
Toast.makeText(AddBookActivity.this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
//This is the function for save Author/s relation of Book object.This function in BookAuthorAdapter.
public ParseRelation<ParseObject> getSelectedItems(ParseObject parseObject) {
ParseRelation<ParseObject> relation = parseObject.getRelation("author_relation");
for (ParseObjectModel object : this.list) {
if (object.isChecked())
relation.add(object.getObject());
}
return relation;
}
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
progressDialog.show()
book.put("genre", genre)
book.put("publisher", publisher!!)
book.put("title", title)
book.put("year", year)
//Here we are setting book relation with getSelectedItem function of BookAuthorAdapter.
if (recyclerViewAuthors.adapter != null) {
relation = (recyclerViewAuthors.adapter as BookAuthorAdapter).getSelectedItems(book)
if (relation == null) {
Toast.makeText(this, "Please select Author/s", Toast.LENGTH_SHORT).show()
return
}
} else {
Toast.makeText(this, "Something went wrong!!", Toast.LENGTH_SHORT).show()
return
}
book.saveInBackground {
progressDialog.hide()
if (it == null) {
Toast.makeText(this, "Book saved successfully", Toast.LENGTH_SHORT).show()
startActivity(Intent(this@AddBookActivity, BookListActivity::class.java))
finish()
} else {
Toast.makeText(this, it.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
//This is the function for save Author/s relation of Book object.This function in BookAuthorAdapter.
fun getSelectedItems(parseObject: ParseObject): ParseRelation<ParseObject>? {
var relation:ParseRelation<ParseObject>? = parseObject.getRelation("author_relation")
for (obj in this.list) {
if (obj.isChecked)
relation?.add(obj.obj)
}
return relation
}
Step 3 - Query the Book Details with Relations
With these functions, we will list our Books
according to their Publishers
. First, we throw a query to the Publisher
class.
1
2
3
4
5
6
7
8
9
10
11
12
progressDialog.show();
ParseQuery<ParseObject> query = new ParseQuery<>("Publisher");
query.findInBackground((objects, e) -> {
progressDialog.hide();
if (e == null) {
BookListAdapter adapter = new BookListAdapter(objects, this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
} else {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
progressDialog.show()
val query = ParseQuery<ParseObject>("Publisher")
query.findInBackground { objects: List<ParseObject>?, e: ParseException? ->
progressDialog.hide()
if (e == null) {
val adapter = BookListAdapter(this, objects!!)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
} else {
Toast.makeText(this, e.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
And then we query to list the Books
that each Publisher
item is related to.
1
2
3
4
5
6
7
8
9
10
11
12
13
ParseObject object = list.get(position);
holder.title.setText(object.getString("name"));
ParseQuery<ParseObject> query = new ParseQuery<>("Book");
query.whereEqualTo("publisher", object);
query.findInBackground((objects, e) -> {
if (e == null) {
BooksAdapter adapter = new BooksAdapter(objects, context);
holder.books.setLayoutManager(new LinearLayoutManager(context));
holder.books.setAdapter(adapter);
} else {
Toast.makeText(context, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
val `object` = list[position]
holder.title.text = `object`.getString("name")
val query = ParseQuery<ParseObject>("Book")
query.whereEqualTo("publisher", `object`)
query.findInBackground { objects: List<ParseObject>?, e: ParseException? ->
if (e == null) {
val adapter = BooksAdapter(context, objects!!)
holder.books.layoutManager = LinearLayoutManager(context)
holder.books.adapter = adapter
} else {
Toast.makeText(context, e.localizedMessage, Toast.LENGTH_SHORT).show()
}
}

Now, when we click on any Book
object, we send the Object Id of this Book
with an intent to the page that will show the details of that Book
. And we get all the details of the Book
from the database by using this Object Id on that page.
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
private void getBookWithDetails() {
progressDialog.show();
ParseQuery<ParseObject> query = new ParseQuery<>("Book");
query.getInBackground(getIntent().getStringExtra("objectId"), (object, e) -> {
if (e == null) {
bookTitle.setText("Title: " +object.getString("title"));
bookYear.setText("Year: " +object.getString("year"));
try {
bookGenre.setText("Genre: " +object.getParseObject("genre").fetchIfNeeded().getString("name"));
} catch (ParseException parseException) {
parseException.printStackTrace();
}
try {
bookPublisher.setText("Publisher: " + object.getParseObject("publisher").fetchIfNeeded().getString("name"));
} catch (ParseException parseException) {
parseException.printStackTrace();
}
object.getRelation("author_relation").getQuery().findInBackground((objects, e1) -> {
progressDialog.hide();
if (e1 == null) {
BookDetailAuthorAdapter adapter = new BookDetailAuthorAdapter(objects, this);
authorRecyclerView.setLayoutManager(new LinearLayoutManager(this));
authorRecyclerView.setAdapter(adapter);
} else {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
} else {
progressDialog.hide();
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
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
private fun getBookWithDetails() {
progressDialog.show()
val query = ParseQuery<ParseObject>("Book")
query.getInBackground(intent.getStringExtra("objectId")) { `object`, e ->
if (e == null) {
bookTitle.text = "Title: " + `object`.getString("title")
bookYear.text = "Year: " + `object`.getString("year")
try {
bookGenre.text = "Genre: " + `object`.getParseObject("genre")?.fetchIfNeeded<ParseObject>()?.getString("name")
} catch (parseException: ParseException) {
parseException.printStackTrace()
}
try {
bookPublisher.text =
"Publisher: " + `object`.getParseObject("publisher")?.fetchIfNeeded<ParseObject>()?.getString("name")
} catch (parseException: ParseException) {
parseException.printStackTrace()
}
`object`.getRelation<ParseObject>("author_relation").query.findInBackground { objects, e1 ->
progressDialog.hide()
if (e1 == null) {
val adapter = BookDetailAuthorAdapter(this, objects)
authorRecyclerView.layoutManager = LinearLayoutManager(this)
authorRecyclerView.adapter = adapter
} else {
Toast.makeText(this, e1.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
} else {
progressDialog.hide()
Toast.makeText(this, e.localizedMessage, Toast.LENGTH_SHORT).show()
}
}
}
It’s done!
At this point, we have learned Parse Relationships
on Android
.