14.1B: Deleting and updating data with Room
Contents:
- What you should already KNOW
- What you will LEARN
- What you will DO
- App overview
- Task 1. Initialize data only if the database is empty
- Task 2. Delete all words
- Task 3. Add an Options menu item to delete all data
- Task 4. Delete a single word
- Task 5. Enable users to swipe words away
- Solution code
- Coding challenge
- Challenge solution code
- Summary
- Related concept
This practical gives you more practice at using the API provided by the Room library to implement database functionality. You will add the ability to delete specific items from the database. The practical also includes a coding challenge, in which you update the app so the user can edit existing data.
What you should already KNOW
You should be able to create and run apps in Android Studio 3.0 or higher. In particular, be familiar with the following:
- Using
RecyclerView
and adapters. - Using entity classes, data access objects (DAOs), and the
RoomDatabase
to store and retrieve data in Android's built-in SQLite database. You learned these topics in the previous practical, 15.1A: Working with Architecture Components: Room, LiveData, ViewModel.
What you will LEARN
You will learn how to:
- Populate the database with data only if the database is empty (so users don't lose changes they made to the data).
- Delete data from a Room database.
- Update existing data (if you build the challenge app).
What you will DO
- Update the RoomWordsSample app to keep data when the app closes.
- Allow users to delete all words by selecting an Options menu item.
- Allow users to delete a specific word by swiping an item in the list.
- Optionally, in a coding challenge, extend the app to allow the user to update existing words.
App overview
You will extend the RoomWordsSample app that you created in the previous practical. So far, that app displays a list of words, and users can add words. When the app closes and re-opens, the app re-initializes the database, and words that the user has added are lost.
In this practical, you extend the app so that it only initializes the data in the database if there is no existing data.
Then you add a menu item that allows the user to delete all the data.
You also enable the user to swipe a word to delete it from the database.
Task 1. Initialize data only if the database is empty
The RoomWordsSample app that you created in the previous practical deletes and re-creates the data whenever the user opens the app. This behavior isn't ideal, because users will want their added words to remain in the database when the app is closed.
In this task you update the app so that when it opens, the initial data set is only added if the database has no data.
To detect whether the database contains data already, you can run a query to get one data item. If the query returns nothing, then the database is empty.
1.1 Add a method to the DAO to get a single word
Currently, the WordDao
interface has a method for getting all the words, but not for getting any single word. The method to get a single word does not need to return LiveData
, because your app will call the method explicitly when needed.
- In the
WordDao
interface, add a method to get any word:@Query("SELECT * from word_table LIMIT 1") Word[] getAnyWord();
Room
issues the database query when thegetAnyWord()
method is called and returns an array containing one word. You don't need to write any additional code to implement it.
1.2 Update the initialization method to check whether data exists
Use the getAnyWord()
method in the method that initializes the database. If there is any data, leave the data as it is. If there is no data, add the initial data set.
PopulateDBAsync
is an inner class inWordRoomDatbase
. InPopulateDBAsync
, update thedoInBackground()
method to check whether the database has any words before initializing the data:@Override protected Void doInBackground(final Void... params) { // If we have no words, then create the initial list of words if (mDao.getAnyWord().length < 1) { for (int i = 0; i <= words.length - 1; i++) { Word word = new Word(words[i]); mDao.insert(word); } } return null; }
- Run your app and add several new words. Close the app and restart it. You should see the new words you added, as the words should now persist when the app is closed and opened again.
Task 2. Delete all words
In the previous practical, you used the deleteAll()
method to clear out all the data when the database opened. The deleteAll()
method was only invoked from the PopulateDbAsync
class when the app started. Make the deleteAll()
method available through the ViewModel
so that your app can call the method whenever it's needed.
Here are the general steps for implementing a method to use the Room
library to interact with the database:
- Add the method to the DAO, and annotate it with the relevant database operation. For the
deleteAll()
method, you already did this step in the previous practical. - Add the method to the
WordRepository
class. Write the code to run the method in the background. - To call the method in the
WordRepository
class, add the method to theWordViewModel
. The rest of the app can then access the method through theWordViewModel
.
2.1 Add deleteAll() to the WordDao interface and annotate it
- In
WordDao
, check that thedeleteAll()
method is defined and annotated with the SQL that runs when the method executes:@Query("DELETE FROM word_table") void deleteAll();
2.2 Add deleteAll() to the WordRepository class
Add the deleteAll()
method to the WordRepository
and implement an AsyncTask
to delete all words in the background.
In
WordRepository
, definedeleteAllWordsAsyncTask
as an inner class. ImplementdoInBackground()
to delete all the words by callingdeleteAll()
on the DAO:private static class deleteAllWordsAsyncTask extends AsyncTask<Void, Void, Void> { private WordDao mAsyncTaskDao; deleteAllWordsAsyncTask(WordDao dao) { mAsyncTaskDao = dao; } @Override protected Void doInBackground(Void... voids) { mAsyncTaskDao.deleteAll(); return null; } }
- In the
WordRepository
class, add thedeleteAll()
method to invoke theAsyncTask
that you just defined.public void deleteAll() { new deleteAllWordsAsyncTask(mWordDao).execute(); }
2.3 Add deleteAll() to the WordViewModel class
You need to make the deleteAll()
method available to the MainActivity
by adding it to the WordViewModel
.
- In the
WordViewModel
class, add thedeleteAll()
method:public void deleteAll() {mRepository.deleteAll();}
Task 3. Add an Options menu item to delete all data
Next, you will add a menu item to enable users to invoke deleteAll()
.
3.1 Add the Clear all data menu option
- In
menu_main.xml
, change the menu optiontitle
andid
, as follows:<item android:id="@+id/clear_data" android:orderInCategory="100" android:title="@string/clear_all_data" app:showAsAction="never" />
In
MainActivity
, implement theonOptionsItemSelected()
method to invoke thedeleteAll()
method on theWordViewModel
object.@Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.clear_data) { // Add a toast just for confirmation Toast.makeText(this, "Clearing the data...", Toast.LENGTH_SHORT).show(); // Delete the existing data mWordViewModel.deleteAll(); return true; } return super.onOptionsItemSelected(item); }
- Run your app. In the Options menu, select Clear all data. All words should disappear.
- Restart the app. (Restart it from your device or the emulator; don't run it again from Android Studio) You should see the initial set of words.
Task 4. Delete a single word
Your app lets users add words and delete all words. In Tasks 4 and 5, you extend the app so that users can delete a word by swiping the item in the RecyclerView
.
Again, here are the general steps to implement a method to use the Room
library to interact with the database:
- Add the method to the DAO, and annotate it with the relevant database operation.
- Add the method to the
WordRepository
class. Write the code to run the method in the background. - To call the method in the
WordRepository
class, add the method to theWordViewModel
. The rest of the app can then access the method through theWordViewModel
.
4.1 Add deleteWord() to the DAO and annotate it
- In
WordDao
, add thedeleteWord()
method:
Because this operation deletes a single row, the@Delete void deleteWord(Word word);
@Delete
annotation is all that is needed to delete the word from the database.
4.2 Add deleteWord() to the WordRepository class
In
WordRepository
, define anotherAsyncTask
calleddeleteWordAsyncTask
as an inner class. ImplementdoInBackground()
to delete a word bycallingdeleteWord()
on the DAO:private static class deleteWordAsyncTask extends AsyncTask<Word, Void, Void> { private WordDao mAsyncTaskDao; deleteWordAsyncTask(WordDao dao) { mAsyncTaskDao = dao; } @Override protected Void doInBackground(final Word... params) { mAsyncTaskDao.deleteWord(params[0]); return null; } }
- In
WordRepository
, add thedeleteWord()
method to invoke theAsyncTask
you just defined.public void deleteWord(Word word) { new deleteWordAsyncTask(mWordDao).execute(word); }
4.3 Add deleteWord() to the WordViewModel class
To make the deleteWord()
method available to other classes in the app, in particular, MainActivity,
add it to WordViewModel
.
- In
WordViewModel
, add thedeleteWord()
method:
You have now implemented the logic to delete a word, but as yet there is no way to invoke the delete-word operation from the app's UI. You fix that next.public void deleteWord(Word word) {mRepository.deleteWord(word);}
Task 5. Enable users to swipe words away
In this task, you add functionality to allow users to swipe an item in the RecyclerView
to delete it.
Use the ItemTouchHelper
class provided by the Android Support Library (version 7 and higher) to implement swipe functionality in your RecyclerView
. The ItemTouchHelper
class has the following methods:
onMove()
is called when the user moves the item. You do not implement any move functionality in this app.onSwipe()
is called when the user swipes the item. You implement this method to delete the word that was swiped.
5.1 Enable the adapter to detect the swiped word
- In
WordListAdapter
, add a method to get the word at a given position.public Word getWordAtPosition (int position) { return mWords.get(position); }
In
MainActivity
, inonCreate()
, create theItemTouchHelper
. Attach theItemTouchHelper
to theRecyclerView
.// Add the functionality to swipe items in the // recycler view to delete that item ItemTouchHelper helper = new ItemTouchHelper( new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { int position = viewHolder.getAdapterPosition(); Word myWord = adapter.getWordAtPosition(position); Toast.makeText(MainActivity.this, "Deleting " + myWord.getWord(), Toast.LENGTH_LONG).show(); // Delete the word mWordViewModel.deleteWord(myWord); } }); helper.attachToRecyclerView(recyclerView);
.
Things to notice in the code:
onSwiped()
gets the position of the ViewHolder
that was swiped:
int position = viewHolder.getAdapterPosition();
Given the position, you can get the word displayed by the ViewHolder
by calling the getWordAtPosition()
method that you defined in the adapter:
Word myWord = adapter.getWordAtPosition(position);
Delete the word by calling deleteWord()
on the WordViewModel
:
mWordViewModel.deleteWord(myWord);
Now run your app and delete some words
- Run your app. You should be able to delete words by swiping them left or right.
Solution code
Android Studio project: RoomWordsWithDelete
Coding challenge
Challenge: Update your app to allow users to edit a word by tapping the word and then saving their changes.
Hints
Make changes in NewWordActivity
You can add functionality to NewWordActivity
, so that it can be used either to add a new word or edit an existing word.
Use an auto-generated key in Word
The Word
entity class uses the word
field as the database key. However, when you update a row in the database, the item being updated cannot be the primary key, because the primary key is unique to each row and never changes. So you need to add an auto-generated id
to use as the primary key
@PrimaryKey(autoGenerate = true)
private int id;
@NonNull
@ColumnInfo(name = "word")
private String mWord;
Add a constructor for Word
that takes an id
Add a constructor to the Word
entity class that takes id
and word
as parameters. Make sure this additional constructor is annotated using @Ignore
, because Room
expects only one constructor by default in an entity class.
@Ignore
public Word(int id, @NonNull String word) {
this.id = id;
this.mWord = word;
}
To update an existing Word
, create the Word
using this constructor. Room
will use the primary key (in this case the id
) to find the existing entry in the database so it can be updated.
In WordDao
, add the update()
method like this:
@Update
void update(Word... word);
Challenge solution code
Android Studio project: Coming soon
Summary
Writing database code
- Room takes care of opening and closing the database connections each time a database operations executes.
- Annotate methods in the data access object (DAO) as
@insert
,@delete
,@update
,@query
. - For simple insertions, updates and deletions, it is enough to add the relevant annotation to the method in the DAO.
For example:
@Delete
void deleteWord(Word word);
@Update
void update(Word... word);
- For queries or more complex database interactions, such as deleting all words, use the
@query
annotation and provide the SQL for the operation.
For example:
@Query("SELECT * from word_table ORDER BY word ASC")
LiveData<List<Word>> getAllWords();
@Query("DELETE FROM word_table")
void deleteAll();
ItemTouchHelper
- To enable users to swipe or move items in a
RecyclerView
, you can use theItemTouchHelper
class. - Implement
onMove()
andonSwipe().
- To identify which item the user moved or swiped, you can add a method to the adapter for the
RecylerView
. The method takes a position and returns the relevant item. Call the method insideonMove()
oronSwipe()
.
Related concept
The related concept documentation is Architecture Components.
Learn more
Entities, data access objects (DAOs), and ViewModel
:
- Defining data using Room entities
- Accessing data using Room DAOs
ViewModel
guideDao
referenceViewModel
reference- To see all the possible annotations for an entity, go to
android.arch.persistence.room
in the Android reference and expand the Annotations menu item in the left nav.
ItemTouchHelper
:
ItemTouchHelper
reference