Project: Forage app

1. Before you begin

This codelab introduces a new app called Forage that you'll build on your own. This codelab walks you through the steps to complete the Forage app project, including project setup and testing within Android Studio.

Prerequisites

  • This project is for students who have completed Unit 5 of the Android Basics in Kotlin course.

What you'll build

  • Add persistence with Room to an existing app by implementing an entity, DAO, ViewModel, and database class.

What you'll need

  • A computer with Android Studio installed.

2. Finished app overview

The completed Forage app allows users to keep track of items, food for example, that they've foraged for in nature. This data is persisted between sessions using Room. You'll use your knowledge of Room and performing read, write, update, and delete operations on a database to implement persistence in the Forage app. The completed app and its functionality is described below.

When the app is first launched, the user is presented with an empty screen containing a recycler view that will display foraged items, as well as a floating button in the bottom right corner to add new items.

3edd87e63c387d88.png

When adding a new item, the user can specify a name, the location where it was found, as well as some additional notes. There's also a checkbox for whether or not the food item is currently in season.

6c0c739569bb3b4f.png

Once an item has been added, it will appear in the recycler view on the first screen.

bcc75e60b70320e8.png

Tapping an item leads to a detail screen which shows the name, location, and notes.

5096995a4921dcac.png

The floating button also changes from a plus symbol to an edit icon. Tapping this button leads to a screen that lets you edit the name, location, notes, and "in season" checkbox. Tapping the delete button will remove the item from the database.

f8c708fed3dede1a.png

While the UI portion of this app has already been implemented, your task is to implement persistence using your knowledge of Room, so that the app will read, write, update, and delete items from the database.

3. Get started

Download the project code

Note that the folder name is android-basics-kotlin-forage-app. Select this folder when you open the project in Android Studio.

To get the code for this codelab and open it in Android Studio, do the following.

Get the code

  1. Click on the provided URL. This opens the GitHub page for the project in a browser.
  2. On the GitHub page for the project, click the Code button, which brings up a dialog.

5b0a76c50478a73f.png

  1. In the dialog, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
  2. Locate the file on your computer (likely in the Downloads folder).
  3. Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.

Open the project in Android Studio

  1. Start Android Studio.
  2. In the Welcome to Android Studio window, click Open an existing Android Studio project.

36cc44fcf0f89a1d.png

Note: If Android Studio is already open, instead, select the File > New > Import Project menu option.

21f3eec988dcfbe9.png

  1. In the Import Project dialog, navigate to where the unzipped project folder is located (likely in your Downloads folder).
  2. Double-click on that project folder.
  3. Wait for Android Studio to open the project.
  4. Click the Run button 11c34fc5e516fb1c.png to build and run the app. Make sure it builds as expected.
  5. Browse the project files in the Project tool window to see how the app is set-up.

4. Set up the project to use Room

Define the Forageable entity

The project already has a Forageable class that defines the app's data (model.Forageable.kt). This class has several properties: id, name, address, inSeason, and notes.

data class Forageable(
   val id: Long = 0,
   val name: String,
   val address: String,
   val inSeason: Boolean,
   val notes: String?
)

However, in order to use this class to store persistent data, you'll need to convert it to a Room entity.

  1. Annotate the class using @Entity with the table name "forageable_database".
  2. Make the id property the primary key. The primary key should be auto-generated.
  3. Set the column name for the inSeason property to "in_season".

Implement the DAO

ForageableDao (data.ForageableDao.kt), as you might guess, is where you define methods for reading and writing from the database that you will access from the view model. As the DAO is only an interface that you define, you won't actually have to write any code to implement these methods. Instead, you should use Room annotations, specifying the SQL query where needed.

Within the ForageableDao interface, you'll need to add five methods.

  1. A getForageables() method that returns a Flow<List<Forageable>> for all rows in the database.
  2. A getForageable(id: Long) method that returns a Flow<Forageable> that matches the specified id.
  3. An insert(forageable: Forageable) method that inserts a new Forageable into the database.
  4. An update(forageable: Forageable) method that takes an existing Forageable as a parameter and updates the row accordingly.
  5. A delete(forageable: Forageable) method that takes a Forageable as a parameter and deletes it from the database.

Implement the view model

The ForageableViewModel (ui.viewmodel.ForageableViewModel.kt) is partially implemented, but you'll need to add functionality that accesses the DAO methods so it can actually read and write data. Perform the following steps to implement ForageableViewModel.

  1. An instance of ForageableDao should be passed as a parameter in the class constructor.
  2. Create a variable of type LiveData<List<Forageable>> that gets the entire list of Forageable entities using the DAO, and converts the result to LiveData.
  3. Create a method that takes an id (of type Long) as a parameter and returns a LiveData<Forageable> from calling the getForageable() method on the DAO, and converting the result to LiveData.
  4. In the addForageable() method, launch a coroutine using the viewModelScope and use the DAO to insert the Forageable instance into the database.
  5. In the updateForageable() method, use the DAO to update the Forageable entity.
  6. In the deleteForageable() method, use the DAO to update the Forageable entity.
  7. Create a ViewModelFactory that can create an instance of ForageableViewModel with a ForageableDao constructor parameter.

Implement the Database class

The ForageDatabase (data.ForageDatabase.kt) class is what actually exposes your entities and DAO to Room. Implement the ForageDatabase class as described.

  1. Entities: Forageable
  2. Version: 1
  3. exportSchema: false
  4. Inside the ForageDatabase class, include an abstract function to return a ForageableDao
  5. Inside the ForageDatabase class, define a companion object with a private variable called INSTANCE and a getDatabase() function that returns the ForageDatabase singleton.
  1. In the BaseApplication class, create a database property that returns a ForageDatabase instance using lazy initialization.

5. Persist and read data from the fragments

Once you've set up your entities, DAO, view model, and defined the database class to expose them to Room, all that's left to do is modify the Fragments to access the view model. You'll need to make changes in three files, one for each screen in the app.

Forageables list

The forageables list screen just requires two things: a reference to the view model, and access to the full list of forageables. Perform the following tasks in ui.ForageableListFragment.kt.

  1. The class already has a viewModel property. However, this is not using the factory you defined in the previous step. You'll need to first refactor this declaration to use the ForageableViewModelFactory.
private val viewModel: ForageableViewModel by activityViewModels {
   ForageableViewModelFactory(
       (activity?.application as BaseApplication).database.foragableDao()
   )
}
  1. Then in onViewCreated(), observe the allForageables property from the viewModel and call submitList() on the adapter where appropriate to populate the list.

Forageable details screen

You'll do almost the same thing for the detail list in ui/ForageableDetailFragment.kt.

  1. Convert the viewModel property to correctly initialize the ForageableViewModelFactory.
  2. In onViewCreated(), call getForageable() on the view model, passing in the id, to get the Forageable entity. Observe the livedata and set the result to the forageable property, and then call bindForageable() to update the UI.

Add and edit forageables screen

Finally, you'll need to do a similar thing in ui.AddForageableFragment.kt. Note that this screen is also responsible for updating and deleting entities. However, these methods from the view model are already called in the correct place. You'll only need to make two changes in this file.

  1. Again, refactor the viewModel property to use ForageableViewModelFactory.
  2. In onViewCreated(), in the if statement block before setting the delete button's visibility, call getForageable() on the view model, passing in the id, and setting the result to the forageable property.

That's all you need to do in the fragments. You can now run your app and should be able to see all the persistence functionality in action.

6. Testing instructions

Running your tests

To run your tests, you can do one of the following.

For a single test case, open up a test case class, PersistenceInstrumentationTests.kt and click the green arrow to the left of the class declaration. You can then select the Run option from the menu. This will run all of the tests in the test case.

3e640ec727599a6d.png

Often you'll only want to run a single test, for example, if there's only one failing test and the other tests pass. You can run a single test just as you would the entire test case. Use the green arrow and select the Run option.

8647a76419540499.png

If you have multiple test cases, you can also run the entire test suite. Just like running the app, you can find this option on the Run menu.

7a925c5e196725bb.png

Note that Android Studio will default to the last target that you ran (app, test targets, etc.) so if the menu still says Run > Run ‘app', you can run the test target, by selecting Run > Run.

90d3ec5ca5928b2a.png

Then choose the test target from the popup menu.

3b1a7d82a55b5f13.png