Android

CRUD Parse objects in Android

Introduction

In this section we are gonna build an Android application that performs basic CRUD operations. CRUD is abbreviation of Create-Read-Update-Delete. When you store data on Parse it is built around ParseObject and each one contains key-value pairs of JSON-compatible data.

The values you can store in Back4App Database could be in type of String, Number, Bool, Array, Object, Date, File, Pointer, Relation and null.

You can get more information about data types by clicking here.

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

Here is a preview of what we are gonna achive :

Crud App

Prerequisites

To complete this tutorial, we need:

Understanding our Todo App

To better understand Parse on Android, you will see the CRUD operations implemented on a ToDo App. The application will have a simple interface, with a title and description text field to register a task and a list of registered tasks. You can update each task’s title or description.

Let’s get started!

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

Step 1 - Create Todo App Template

Go to Android Studio, and find activity_main.xml layout file from the Project (Project/app/res/layout/activity_main.xml) and then replace the code below with your own code. This xml code will be our MainActivity's design, and we will bind this views to our MainActivity.java class and use them.

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
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!--Title-->>
    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/blue_700"
        android:gravity="center_horizontal"
        android:padding="24dp"
        android:text="TODO List"
        android:textColor="@color/white"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!--We will open a pop-up view when clicked to this button-->>
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        style="@style/Widget.MaterialComponents.FloatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:scaleType="centerInside"
        android:src="@drawable/ic_baseline_add_24"
        app:backgroundTint="@color/blue_700"
        app:fabSize="normal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:tint="@color/white" />

    <!--We will show this text view when data list is empty-->>
    <TextView
        android:id="@+id/empty_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="List is empty"
        android:layout_marginTop="32dp"
        android:textSize="20sp"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <!--We will adapt the data list to this RecyclerView-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView">
    </androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>

Step 2 - Create Object

The create function will create a new Task with the title and description.

To add a TODO, we enter the title and description values in the pop-up that appears on the screen, set it to the ParseObject and save this object. We save this object in the database by using the function that Parse has provided us.

Note:

  • In this project we will make our own custom alert dialog. You can design your Alert dialog as you want and set it to the view of the dialog you will use later.

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
   openInputPopupDialogButton.setOnClickListener(fabButtonView -> {
            AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
            alertDialogBuilder.setTitle("Create a TODO");
            alertDialogBuilder.setCancelable(true);
            initPopupViewControls();
            //We are setting our custom popup view by AlertDialog.Builder
            alertDialogBuilder.setView(popupInputDialogView);
            final AlertDialog alertDialog = alertDialogBuilder.create();
            alertDialog.show();
            saveTodoButton.setOnClickListener(saveButtonView -> saveTodo(alertDialog));
            cancelUserDataButton.setOnClickListener(cancelButtonView -> alertDialog.cancel());
        });   

    //This is our saveTodo function
    private void saveTodo(AlertDialog alertDialog) {
        ParseObject todo = new ParseObject("Todo");
        if (titleInput.getText().toString().length() != 0 && descriptionInput.getText().toString().length() != 0) {
            alertDialog.cancel();
            progressDialog.show();
            todo.put("title", titleInput.getText().toString());
            todo.put("description", descriptionInput.getText().toString());
            todo.saveInBackground(e -> {
                progressDialog.dismiss();
                if (e == null) {
                    //We saved the object and fetching data again
                    getTodoList();
                } else {
                    //We have an error.We are showing error message here.
                    showAlert("Error", e.getMessage());
                }
            });
        } else {
            showAlert("Error", "Please enter a title and description");
        }
    }
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
   openInputPopupDialogButton?.setOnClickListener { fabButtonView ->
            val alertDialogBuilder = AlertDialog.Builder(this@MainActivity)
            alertDialogBuilder.setTitle("Create a TODO")
            alertDialogBuilder.setCancelable(true)
            initPopupViewControls()
            //We are setting our custom popup view by AlertDialog.Builder
            alertDialogBuilder.setView(popupInputDialogView)
            val alertDialog = alertDialogBuilder.create()
            alertDialog.show()
            saveTodoButton?.setOnClickListener { saveButtonView ->
                saveData(alertDialog)
            }
            cancelUserDataButton?.setOnClickListener { cancelButtonView ->
                alertDialog.cancel()
            }
        }

    //This is our saveTodo function
    private fun saveData(alertDialog: AlertDialog) {
        val todo = ParseObject("Todo")
        if (titleInput?.text.toString().isNotEmpty() && descriptionInput?.text.toString().isNotEmpty()) {
            alertDialog.cancel()
            progressDialog?.show()
            todo.put("title", titleInput?.text.toString())
            todo.put("description", descriptionInput?.text.toString())
            todo.saveInBackground { e ->
                progressDialog?.dismiss()
                if (e == null) {
                    //We saved the object and fetching data again
                    getTodoList()
                } else {
                    //We have an error.We are showing error message here.
                    showAlert("Error", e.message!!)
                }
            }
        } else {
            showAlert("Error", "Please enter a title and description")
        }
    }

Step 3 - Read Object

With the function that Parse has provided us, we can fetch all the data in a class as ParseObject list. We create a query like the one below and fetch all the data in the Todo class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   private void getTodoList() {
        progressDialog.show();
        ParseQuery<ParseObject> query = ParseQuery.getQuery("Todo");
        query.orderByDescending("createdAt");
        query.findInBackground((objects, e) -> {
            progressDialog.dismiss();
            if (e == null) {
                //We are initializing Todo object list to our adapter
                initTodoList(objects);
            } else {
                showAlert("Error", e.getMessage());
            }
        });
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
   private fun getTodoList() {
        progressDialog?.show()
        val query = ParseQuery.getQuery<ParseObject>("Todo")
        query.orderByDescending("createdAt")
        query.findInBackground { objects, e ->
            progressDialog?.dismiss()
            if (e == null) {
                //We are initializing Todo object list to our adapter
                initTodoList(objects)
            } else {
                showAlert("Error", e.message!!)
            }
        }
    }

In this function, we give the returned list as a parameter to our adapter, set this adapter, to our RecyclerView, and we print the values of each object of this list into its own views. And if list has no item, we are setting our empty_text, view’s as VISIBLE.

1
2
3
4
5
6
7
8
9
10
11
12
   private void initTodoList(List<ParseObject> list) {
        if (list == null || list.isEmpty()) {
            empty_text.setVisibility(View.VISIBLE);
            return;
        }
        empty_text.setVisibility(View.GONE);

        TodoAdapter adapter = new TodoAdapter(list, this);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);
    }
1
2
3
4
5
6
7
8
9
10
11
12
   private fun initTodoList(list: List<ParseObject>?) {
        if (list == null || list.isEmpty()) {
            empty_text!!.visibility = View.VISIBLE
            return
        }
        empty_text?.visibility = View.GONE

        val adapter = TodoAdapter(list as ArrayList<ParseObject>, this)

        recyclerView?.layoutManager = LinearLayoutManager(this)
        recyclerView?.adapter = adapter
    }

Step 4 - Update Object

With the edit button in the view of our adapter, we are performing our updates. With the MutableLivedata, object provided by Android, we can listen to the click event of the edit button on our home page. When this button is clicked, we open the same pop-up and this time we populate this pop up with the title and description values of the object we clicked. Then, when the user changes this description and title and press save, first we fetch from the database with the id of this object, then we set the new variables and save object again.

Note:

  • MutableLiveData is a subclass of LiveData which is used for some of it’s properties (setValue/postValue) and using these properties we can easily notify the ui.

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
   adapter.onEditListener.observe(this, parseObject -> {
            AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
            alertDialogBuilder.setTitle("Update a TODO");
            alertDialogBuilder.setCancelable(true);
            //We are initializing PopUp Views with title and description parameters of ParseObject
            initPopupViewControls(parseObject.getString("title"), parseObject.getString("description"));
            alertDialogBuilder.setView(popupInputDialogView);
            final AlertDialog alertDialog = alertDialogBuilder.create();
            alertDialog.show();
            saveTodoButton.setOnClickListener(saveTodoButtonView -> {
                if (titleInput.getText().toString().length() != 0 && descriptionInput.getText().toString().length() != 0) {
                    alertDialog.cancel();
                    progressDialog.show();
                    parseObject.put("title", titleInput.getText().toString());
                    parseObject.put("description", descriptionInput.getText().toString());
                    parseObject.saveInBackground(e1 -> {
                        progressDialog.dismiss();
                        if (e1 == null) {
                            getTodoList();
                        } else {
                            showAlert("Error", e1.getMessage());
                        }
                    });
                } else {
                    showAlert("Error", "Please enter a title and description");
                }
            });
            cancelUserDataButton.setOnClickListener(cancelButtonView -> alertDialog.cancel());
        });
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
   adapter.clickListenerToEdit.observe(this@MainActivity, { parseObject ->
            val alertDialogBuilder = AlertDialog.Builder(this@MainActivity)
            alertDialogBuilder.setTitle("Update a TODO")
            alertDialogBuilder.setCancelable(true)

            //We are initializing PopUp Views with title and description parameters of ParseObject

            initPopupViewControls(
                parseObject.getString("title")!!,
                parseObject.getString("description")!!
            )

            alertDialogBuilder.setView(popupInputDialogView)
            val alertDialog = alertDialogBuilder.create()
            alertDialog.show()

            saveTodoButton?.setOnClickListener { saveButtonView ->
                if (titleInput?.text.toString().isNotEmpty() && descriptionInput?.text.toString().isNotEmpty()) {
                    alertDialog.cancel()
                    progressDialog?.show()
                    parseObject.put("title", titleInput?.text.toString())
                    parseObject.put("description", descriptionInput?.text.toString())
                    parseObject.saveInBackground { e1 ->
                        progressDialog?.dismiss()
                        if (e1 == null) {
                            getTodoList()
                        } else {
                            showAlert("Error", e1.message!!)
                        }
                    }
                } else {
                    showAlert("Error", "Please enter a title and description")
                }
            }
        })

Step 5 - Delete Object

We listen to the click event of the delete button in the view of our adapter with the MutableLivedata object from the home page, as in the edit button. When the delete button is clicked, we give the object id of the ParseObject as a parameter to the delete function that Parse has provided us and delete this object from the database.

1
2
3
4
5
6
7
8
9
10
11
12
   adapter.onDeleteListener.observe(this, parseObject -> {
            progressDialog.show();
            parseObject.deleteInBackground(e -> {
                progressDialog.dismiss();
                if (e == null) {
                    //We deleted the object and fetching data again.
                    getTodoList();
                } else {
                    showAlert("Error",e.getMessage());
                }
            });
        });
1
2
3
4
5
6
7
8
9
10
11
12
   adapter.onDeleteListener.observe(this@MainActivity, { parseObject ->
            progressDialog?.show()
            parseObject.deleteInBackground { e ->
                progressDialog?.dismiss()
                if (e == null) {
                    //We deleted the object and fetching data again.
                    getTodoList()
                } else {
                    showAlert("Error", e.message!!)
                }
            }
        })

It’s done!

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