How to create a Sound Recording Application using ObjectBox as DB in android studio (Final Part)

in #utopian-io6 years ago (edited)

logo.png

Repository

https://github.com/objectbox/objectbox-java

What Will I Learn?

  • How to create a Sound Recording application using ObjectBox DB.
  • How to use Query Builders.
  • How to Delete ObjectBox objects.
  • How to register a FileObserver on a Folder.
  • How to use the .equal() Query to filter ObjectBox Objects.

Resources

Difficulty

  • Intermediate

Tutorial Duration - 30 - 35Mins

Tutorial Content

This happens to be the part four and final part of this tutorial series and the part one can be found here and the part two here and the part three here

In today's tutorial, we are going to be adding more functionality to our Application which can currently record, and then users can play their recordings and they can rename recordings which will have an effect both in the application and also in the file directory of the users phone.

Today, we are going to be adding a delete function in our application which would enable users to delete recordings.We will then include a DirectoryFileObserver on the directory where we save our recordings incase the user deletes a recording outside of our application we also need to delete the recording inside the application also.

Outline

  • Implement the DELETE functionality in our custom Adapter.
  • Include the FileObserver in our SavedRecordingsFragment java class file.

Implement the DELETE functionality.

To implement the DELETE functionality, we are going to be adding a delete option to the setOnLongClickListener which was registered on our cardview but before we do that, we have to make some minor changes to our SavedRecordingsAdapter file which is making our RECORDING object which happens to be our Recordings box gotten from our MyApplicationClass file to a global variable.

To achieve the above, add this (private Box<Recordings> RECORDINGS;) to our list of already existing private variables and edit our adapter constructor to the following -

public SavedRecordingsAdapter(Context context, List<Recordings> recordings) {
    super();
    mContext = context;
    this.recordings = recordings;

    //Get a Box for the Recordings.class POJO
    RECORDINGS = ((MyApplicationClass)mContext.getApplicationContext()).getBoxStore().boxFor(Recordings.class);
}

With that done, we need to add the delete option to our cardview's setOnLongClickListener so follow the below guidelines -

  1. Add another String to our option_entries ArrayList object - option_entries.add(mContext.getString(R.string.dialog_file_delete));
    NB: the R.string.dialog_file_delete has a value of - Delete
  2. We need to edit our setItems() method for our builder (AlertDialog.Builder) object to the following -
builder.setItems(items, new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog, int item) {
        if (item == 0) {
            renameFileDialog(holder.getAdapterPosition());
        }
        if (item == 1) {
            deleteFileDialog(holder.getAdapterPosition());
        }
    }
});

NB: As explained in the part three of this tutorial, the above code registers an onClick listener to our items and here if the user clicks on the second item which will have the index number of one (1), we call a method - deleteFileDialog) and pass the current position of the Adapter which can be gotten from - holder.getAdapterPosition() as an arguement.


deleteFileDialog()

public void deleteFileDialog (final int position) {
    // File delete confirm
    AlertDialog.Builder confirmDelete = new AlertDialog.Builder(mContext);
    confirmDelete.setTitle(mContext.getString(R.string.dialog_title_delete));
    confirmDelete.setMessage(mContext.getString(R.string.dialog_text_delete));
    confirmDelete.setCancelable(true);
    confirmDelete.setPositiveButton(mContext.getString(R.string.dialog_action_yes),
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    try {
                        //remove item from ObjectBox, RecyclerView, and storage (user's phone)
                        remove(position);

                    } catch (Exception e) {
                        Log.e(LOG_TAG, "exception", e);
                    }

                    dialog.dismiss();
                }
            });
    confirmDelete.setNegativeButton(mContext.getString(R.string.dialog_action_no),
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    dialog.dismiss();
                }
            });

    AlertDialog alert = confirmDelete.create();
    alert.show();
}

Code Explanation

  1. Firstly, we create an AlertDialog.Builder object - confirmDelete which we set its title to a String with the id - R.string.dialog_title_delete which has the value of Delete Recording and then we set the message of the builder to a String with the id - R.string.dialog.dialog_text_delete which has a value of - Are you sure you would like to delete this file? and we then set the dialog to be cancelable by setting its isCancelable() method to true.
  2. We then set the text of the positive button to a String with the id - R.string.dialog_action_yes which has a value of - Yes and we then set an onCLickListener which will then call the method - remove() with position as its argumnent which holds the position of the recording in our RecyclerView we intend to delete.
  3. We then set the negetiveButton of our dialog to a String with the id of *R.string.dialog_action_no which has the value of - No and then the onClick listener dismisses the dialog with - dialog.dismiss()
  4. Lastly, we create the Dialog using the dialog builder object and then we show the dialog - alert.show().

remove()

private void remove(int position) {
    //remove item from database, RecyclerView and storage

    //delete file from storage
    File file = new File(getItem(position).getRecording_path());
    file.delete();

    Toast.makeText(mContext, String.format(mContext.getString(R.string.toast_file_delete), getItem(position).getRecording_name()),Toast.LENGTH_SHORT).show();

    //Get the particular box item that we intend to delete
    Recordings recordingToDelete = RECORDINGS.get(recording.getId());

    //Delete the object from the ObjectBox
    RECORDINGS.remove(recordingToDelete);
    notifyItemRemoved(position);
}

Code Explanation

  1. We create a File object (file) by gettting the path of the recording that it's position was passed to the method by calling the getRecording_path() which we then delete by - file.delete().
  2. We then show a Toast to the user telling the user that the recording has been deleted.
  3. Next thing we have to do now is to delete the recording from our ObjectBox database. To do that, we create a new Recordings object - recordingToDelete which will be the recording that it's id property corresponds to the one with the id of the recording that was long pressed on.
  4. We then remove the recording from our ObjectBox database by calling the remove() method on the RECORDINGS object which we just made global a few steps ago and passing the recordingToDelete object to it - RECORDINGS.remove(recordingToDelete).
  5. Lastly, we call the notifyItemRemoved() method and pass the position of the item that has been removed.

Having done all this, the next thing we have to do now is to set a FileObeserver on the directory where we save our recordings on the users phone will will then listen if any recording has been deleted and then delete the same recording from the database.

To achieve the following, we have to open our - SavedRecordingsFragment class file and add the below class to the exisiting code -

class DirectoryFileObserver extends FileObserver {
    private String asbsolutePath;

    public DirectoryFileObserver(String path) {
        super(path,FileObserver.DELETE);
        asbsolutePath = path;
    }

    @Override
    public void onEvent(int event, @Nullable String path) {
        event &= FileObserver.ALL_EVENTS;
        if((FileObserver.DELETE & event) != 0 ){
            // user deletes a recording file out of the app

            String filePath = android.os.Environment.getExternalStorageDirectory().toString()
                    + "/Soundbox/" + path;

            Log.d("SoundBox/", "File deleted ["
                    + android.os.Environment.getExternalStorageDirectory().toString()
                    + "/Soundbox/" + path + "]");

            // remove file from database and recyclerview
            savedRecordingsAdapter.deleteRecordingWithPath(filePath);
        }
    }
}

Code Explanation

  1. The DirectoryFileObserver class extends the FileObserver class, in our constructor, we set the private variable - absolutePath to the value of the path passed into the constructor and we then call the super method with the path sent and also a mask of FileObserver.DELETE which listens for DELETE operations.
  2. We then override the onEvent method of the class and we use an if statement to check if the operation performed was a delete operation if it was, we get the path of the file deleted and then we call our Adapters deleteRecordingWithPath() method passing the path of the file as its method.
    NB: the deleteRecordingWithPath() method will be created in the next section.

< hr />

deleteRecordingWithPath()
Lastly for this turoial, we need to setup the deleteRecordingWithPath() method in our custom adapter - SavedRecordingsAdapter.Before the onBindViewHolder() method, add the following code -

public void deleteRecordingWithPath(String filePath) {
    //user deletes a saved recording out of the application through another application
    Recordings record = RECORDINGS.query().equal(Recordings_.recording_path,filePath).build().findFirst();

    if (record != null){
        RECORDINGS.remove(record.getId());
        notifyDataSetChanged();
    }
}

Code Explanation

  1. First, we use the RECORDINGS object which is an Box of Recordings type to get the actual recording that its path matches the path sent in the method and save it in an object - record
    • We achieve the above by calling a qyuery on the RECORDINGS object and then using the equal() method where we specified the field we intend to search through which is the recording_path field of the class which can be gotten by - Recordings_.recording_path,filePath. We then search through the field looking for a direct match to the recording with the value of the - filePath variable, we then build and call the findFirst() method which returns the first recording to match the query.
  2. Next, we use an if statement to ensure that the record object is not null after which we remove the recording from our RECORDING object and calll the notifyDataSetChanged() method which tells the adapter that a change has occured.

APPLICATION EXECUTION

Proof of Work
https://github.com/generalkolo/SoundRecorderObjectBox

Curriculum

Sort:  

Thank you for your contribution.

  • Please edit your tutorial and remove from the middle of your tutorial the tag < hr />
  • Why not put your app videos on the Dtube platform? Using the Steem platforms is good for all of us.

Very good work with these tutorials "How to create a Sound Recording Application using ObjectBox as DB in android studio".

Looking forward to your upcoming tutorials.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thanks @portugalcoin for taking time out to moderate my contribution.
Would start using Dtube for my uploads for upcoming tutorials.

Thank you for your review, @portugalcoin!

So far this week you've reviewed 14 contributions. Keep up the good work!

Hey @edetebenezer
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Hi @edetebenezer! We are @steem-ua, a new Steem dApp, computing UserAuthority for all accounts on Steem. We are currently in test modus upvoting quality Utopian-io contributions! Nice work!

Congratulations @edetebenezer! You received a personal award!

1 Year on Steemit

Click here to view your Board of Honor

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @edetebenezer! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

You made more than 900 upvotes. Your next target is to reach 1000 upvotes.

Click here to view your Board of Honor
If you no longer want to receive notifications, reply to this comment with the word STOP

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @edetebenezer! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!