Homework 3 - Plant care app with Firebase Firestore cloud storage
This homework is based on the project started in Lab 5 . It gives you some base code which is helpful to get started for this HW. The goal is to learn basic usage of Firestore and instead of using a Room local database (done in the lab) with a Firestore DB.
- Note: https://firebase.google.com/docs/firestore/quickstart is a good support material for the following steps and this homework in general!
Setting up Firebase ( 1 pts)
We will need to create a new Firebase project and use Firestore database for it. This requires a Google account.
Show steps for creating the Firebase project online
Go to https://console.firebase.google.com/ and create a new project
- You can turn off Analytics when asked
- When the project has been created, select the Android icon under "Get started by adding Firebase to your app ". At this stage you should also create a new Android Studio project or have the lab project handy.
- Provide the package name of the new App (ee.ut.cs.<<myAppName>> , for example). The one provided to Firebase MUST match the actual one used in the app!
- After proceeding, you will be presented a google-services.json file. This contains necessary data for your app to securely connect to Firebase.
- The registration wizard on the webpage provides shows guidelines how to add the .json to your Android Studio project. Follow the instructions.
- Follow the the next steps instructions regarding necessary SDK / Gradle dependencies. Note that you have to modify 2 build.gradle files!
- For the app-level build.gradle dependencies{ .. },
implementation 'com.google.firebase:firebase-firestore-ktx:23.0.3'
is sufficient.
- For the app-level build.gradle dependencies{ .. },
Once you finish the above steps, on Firebase website / web console, add Firestore to your app (E.g. left-hand side menu Firestore Database -> Create Database )
- When asked about production or test mode, you can choose test mode, which gives read & write access to everybody who can reach your DB! Check here for more details on security in firestore.
- For region (firestore location), you can choose something in Europe.
Try to create a collection and add some documents from the web project manager.
Verifying your App can use Firestore (show steps)
Just for testing, let's try adding some data, e.g. from our App's MainActivity (onCreate or similar).
In ViewModel (or Activity), try to access the database:
// Get instance of database val db = FirebaseFirestore.getInstance()
- We reference a collection named "cities" (it will be created if it doesn't exist) and add 2 documents to it with this code: (Show Code)
val cities = db.collection("cities") val dataTallinn = hashMapOf( "name" to "Tallinn", "population" to 434562, "area_km2" to 159 ) cities.document("TLL").set(dataTallinn) .addOnSuccessListener { Log.d(TAG, "Document successfully written!") } .addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) } val dataTartu = hashMapOf( "name" to "Tartu", "population" to 93865, "area_km2" to 39 ) cities.document("TRT").set(dataTartu)
(You may comment out the code once it has been created on the first run, Firestore doesn't raise exceptions by default when inserting duplicate data).
After running your app with the above steps, the data should become visible in the web dashboard of Firestore. This part was just to test you have set up Firestore correctly, you should delete it for the final HW solution.
Task Requirements
Now, study the Firestore Getting Started Guide and other Docs and try to update the project so that the following functionality works and is using Firestore:
- Listing all Plants in the DB in MainActivity (2 pts)
- Should use a query that queries for all plants (all Firestore documents in the plants collection), but sorts them by plantedTime!
- Most recently planted should appear first (at top of list)
- Creation of new Plants ( 1pt)
- Insert 1 new document based on the user-entered values, should use a query similar to the 'cities' example above
- Display of Plant Details view ( 1 pts)
- Should work based on a query that selects/filters by a single ID! (get a document by its ID)
- Note that Firestore document ID-s are Strings, not Ints
- Should work based on a query that selects/filters by a single ID! (get a document by its ID)
- Notifying user about DB changes. (2 pts)
- Implement a Service (started Service), which:
- Every minute, queries Firestore for the newest plant document in the DB.
- Use ordering and limiting of data, you should query for exactly 1 document.
- If the newest document is different compared to the last query by the service, use sendBroadcast to notify the user about a new document appearing.
- All Activities should be able to listen to the broadcast and notify the user, using a Snackbar message about the dataset having changed.
- The service should be started when MainActivity (PlantListActivity) is created, and service should be stopped when MainActivity is destroyed.
- To avoid getting notifications about the plant you yourself just added to the DB, you can call startService with Intent extras after inserting a new plant, to let the service know that for this plant, the change can be ignored.
- Every minute, queries Firestore for the newest plant document in the DB.
- Implement a Service (started Service), which:
The idea of the service is to notify the user about somebody else changing the dataset.
Note: Firestore already supports real-time updates, but we with this Service task, we are asking you not to use that, and instead create our own solution as described above.
Non-Functional requirements:
- The Firebase-related code needed for UI should be placed in a ViewModel (1 pt)
- You can use a single ViewModel for both Plant list and Plant details, or create 2 separate ones, it's up to you.
- For service, you can directly use the Firestore instance.
Tips
Some pointers
- How to query/read data and log it?
- How to sort results?
- What are documents and collections?
- Lecture 5 slides PDF has some basic examples of Firestore usage at the end.
Tip: Updating the UI/Activity/Adapter after getting Firebase query results
The Firebase library makes requests to the DB asynchronously and you get the results in callback methods (e.g. via .addOnSuccessListener { result -> //do something with the query result
} .
This means the UI should be notified that new data is available inside the callback, not right after the code block defining the request. In the lab, we set Room not make requests asynchronously, this means that code like this:
// MainActivity override fun onResume() { super.onResume() model.refresh() // uses DB to update model.plantsArray plantsAdapter.data = model.plantArray plantsAdapter.notifyDataSetChanged()
}
will not work well, since the model.refresh() with firebase will do most of its work asynchronously, meaning the next lines of code which update the adapter, will be run before the DB query has finished.
You may try adding your own callback (As described in Lecture 6) to refresh() to overcome this. That callback will then update and notify the adapter.
Tip: Data Type returned by Firestore
Firestore returns documents as (Hash)Maps, so a straight-forward way is to manually construct PlantEntities based on the various values (title, author, etc) contained in the HashMap.
Alternatively you could replace PlantEntity entirely by HashMap. So, instead of having Array<PlantEntity> in viewmodel, you might have Array<HashMap> instead.
Another option is to tell Firebase which Custom data class (e.g. PlantEntitiy) to translate the query responses to, for more info see here: https://firebase.google.com/docs/firestore/query-data/get-data#custom_objects
- You should remove all previous Room-related classes and code (Dao, LocalPlantDb). You can re-use the PlantEntity data class, but remove the room @-annotations.
Other Troubleshooting
If you get an error about no of method references exceeding 65,536 methods after adding the Google services libraries, you probably have to enable multidex:
https://developer.android.com/studio/build/multidex#mdex-gradle
Submission
Create a sub-folder particularly for this homework (call it homework3), it should contain the source code of your Android Studio project.
- Create a new Git TAG named
homework3
for the last commit related to this homework. - Update the .MD file at the root of your repository, so it contains a link to the subfolder of this homework.