Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Android SDK
Code

Create a Weight Tracker App With Cloud Firestore

by
Difficulty:IntermediateLength:LongLanguages:

Storing your app's data in the cloud is very important these days because users tend to own multiple devices and want their apps to be in sync across all of them. With Cloud Firestore, a real-time NoSQL database available on the Firebase platform, doing so is easier and more secure than ever before.

In an earlier tutorial, I introduced you to all the powerful features Cloud Firestore has to offer. Today, I'll show you how to use it alongside other Firebase products, such as FirebaseUI Auth and Firebase Analytics, to create a simple, yet highly scalable, weight tracker app.

Prerequisites

To follow this step-by-step tutorial, you'll need:

  • the latest version of Android Studio
  • a Firebase account
  • and a device or emulator running Android 5.0 or higher

1. Project Setup

To be able to make use of Firebase products in your Android Studio project, you will need the Google Services Gradle plugin, a Firebase configuration file, and a few implementation dependencies. With the Firebase Assistant, you can get them all very easily.

Open the assistant by going to Tools > Firebase. Next, select the Analytics option and click on the Log an Analytics Event link.

Fierbase Assistant panel

You can now press the Connect to Firebase button to connect your Android Studio project to a new Firebase project.

Connect to Firebase dialog

However, to actually add the plugin and the implementation dependencies, you'll need to also press the Add Analytics to your app button.

The weight tracker app we are creating today is going to have just two features: storing weights and displaying them as a list sorted in reverse chronological order. We will, of course, be using Firestore to store the weights. To display them as a list, however, we'll use Firestore-related components available in the FirebaseUI library. Therefore, add the following implementation dependency to the app module's build.gradle file:

Users must be able to view only their own weights, not the weights of everyone who uses the app. Therefore, our app needs to have the ability to uniquely identify its users. FirebaseUI Auth offers this ability, so add the following dependency next:

We will also be needing a few Material Design widgets to give our app a pleasing look. So make sure you add the Design Support library and the Material Dialogs library as dependencies.

Finally, press the Sync Now button to update the project.

2. Configuring Firebase Authentication

Firebase Authentication supports a variety of identity providers. However, all of them are disabled by default. To enable one or more of them, you must visit the Firebase console.

In the console, select the Firebase project you created in the previous step, go to its Authentication section, and press the Set up sign-in method button.

Firebase Authentication home screen

To allow users to log in to our app using a Google account, enable Google as a provider, give a meaningful public-facing name to the project, and press the Save button.

Google identity provider configuration

Google is the easiest identity provider you can use. It needs no configuration, and your Android Studio project won't need any additional dependencies for it.

3. Configuring Cloud Firestore

You must enable Firestore in the Firebase console before you start using it. To do so, go to the Database section and press the Get Started button present in the Cloud Firestore Beta card.

Cloud Firestore card

You will now be prompted to select a security mode for the database. Make sure you choose the Start in locked mode option and press the Enable button.

Security mode selection screen

In the locked mode, by default, no one will be able to access or modify the contents of the database. Therefore, you must now create a security rule that allows users to read and write only those documents that belong to them. Start by opening the Rules tab.

Before we create a security rule for our database, we must finalize how we are going to store data in it. So let's say we're going to have a top-level collection named users containing documents that represent our users. The documents can have unique IDs that are identical to the IDs that the Firebase Authentication service generates for the users.

Because the users will be adding several weight entries to their documents, using a sub-collection to store those entries is ideal. Let's call the sub-collection weights.

Firestore database structure

Based on the above schema, we can now create a rule for the path users/{user_id}/weights/{weight}. The rule will be that a user is allowed to read from and write to the path only if the {user_id} variable is equal to the Firebase Authentication ID of the user.

Accordingly, update the contents of the rules editor.

Finally, press the Publish button to activate the rule.

4. Authenticating Users

Our app must be usable only if the user is logged in to it using a Google account. Therefore, as soon as it opens, it must check if the user has a valid Firebase Authentication ID. If the user does have the ID, it should go ahead and render the user interface. Otherwise, it should display a sign-in screen.

To check if the user has an ID, we can simply check that the currentUser property of the FirebaseAuth class is not null. If it is null, we can create a sign-in intent by calling the createSignInIntentBuilder() method of the AuthUI class.

The following code shows you how to do so for Google as the identity provider:

Note that we're calling a method named showUI() if a valid ID is already present. This method doesn't exist yet, so create it and leave its body empty for now.

To catch the result of the sign-in intent, we must override the onActivityResult() method of the activity. Inside the method, if the value of the resultCode argument is RESULT_OK and the currentUser property is no longer null, it means that the user managed to sign in successfully. In this case, we must again call the showUI() method to render the user interface.

If the user fails to sign in, we can display a toast and close the app by calling the finish() method.

Accordingly, add the following code to the activity:

At this point, if you run the app for the first time, you should be able to see a sign-in screen that looks like this:

Account selection dialog

On subsequent runs—thanks to Google Smart Lock, which is enabled by default—you will be signed in automatically.

5. Defining Layouts

Our app needs two layouts: one for the main activity and one for the weight entries that will be shown as items of the reverse chronologically ordered list.

The layout of the main activity must have a RecyclerView widget, which will act as the list, and a FloatingActionButton widget, which the user can press to create a new weight entry. After placing them both inside a RelativeLayout widget, your activity's layout XML file should look like this:

We have associated an on-click event handler named addWeight() with the FloatingActionButton widget. The handler doesn't exist yet, so create a stub for it inside the activity.

To keep the layout of the weight entry simple, we're going to have just two TextView widgets inside it: one to display the weight and the other to display the time at which the entry was created. Using a LinearLayout widget as a container for them will suffice.

Accordingly, create a new layout XML file named weight_entry.xml and add the following code to it:

6. Creating a Model

In the previous step, you saw that each weight entry has a weight and time associated with it. To let Firestore know about this, we must create a model for the weight entry.

Firestore models are usually simple data classes with the required member variables.

Now is also a good time to create a view holder for each weight entry. The view holder, as you might have guessed, will be used by the RecyclerView widget to render the list items. So create a new class named WeightEntryVH, which extends the RecyclerView.ViewHolder class, and create member variables for both the TextView widgets. Don't forget to initialize them using the findViewById() method. The following code shows you how to do concisely:

7. Creating Unique User Documents

When a user tries to create a weight entry for the first time, our app must create a separate document for the user inside the users collection on Firestore. As we decided earlier, the ID of the document will be nothing but the user's Firebase Authentication ID, which can be obtained using the uid property of the currentUser object.

To get a reference to the users collection, we must use the collection() method of the FirebaseFirestore class. We can then call its document() method and pass the uid as an argument to create the user's document.

We will need to access the user-specific documents both while reading and creating the weight entries. To avoid coding the above logic twice, I suggest you create a separate method for it.

Note that the document will be created only once per user. In other words, multiple calls to the above method will always return the same document, so long as the user uses the same Google account.

8. Adding Weight Entries

When users press the floating action button of our app, they must be able to create new weight entries. To allow them to type in their weights, let us now create a dialog containing an EditText widget. With the Material Dialog library, doing so is extremely intuitive.

Inside the addWeight() method, which serves as the on-click event handler of the button, create a MaterialDialog.Builder instance and call its title() and content() methods to give your dialog a title and a meaningful message. Similarly, call the inputType() method and pass TYPE_CLASS_NUMBER as an argument to it to make sure that the user can type in only numbers in the dialog.

Next, call the input() method to specify a hint and associate an event handler with the dialog. The handler will receive the weight the user typed in as an argument.

Finally, make sure you call the show() method to display the dialog.

Inside the event handler, we must now add code to actually create and populate a new weight entry document. Because the document must belong to the weights collection of the user's unique document, in order to access the collection, you must call the collection() method of the document that's returned by the getUserDocument() method.

Once you have the collection, you can call its add() method and pass a new instance of the WeightEntry class to it to store the entry.

In the above code, you can see that we are using the time property of the Date class to associate a timestamp with the entry.

If you run the app now, you should be able to add new weight entries to Firestore. You won't see them in the app just yet, but they will be visible in the Firebase console.

Add weight dialog

9. Displaying the Weight Entries

It is now time to populate the RecyclerView widget of our layout. So start by creating a reference for it using the findViewById() method and assigning a new instance of the LinearLayoutManager class to it. This must be done inside the showUI() method we created earlier.

The RecyclerView widget must display all the documents that are present inside the weights collection of the user's document. Furthermore, the latest documents should appear first. To meet these requirements, we must now create a query by calling the collection() and orderBy() methods.

For the sake of efficiency, you can limit the number of values returned by the query by calling the limit() method.

The following code creates a query that returns the last 90 weight entries created by the user:

Using the query, we must now create a FirestoreRecyclerOptions object, which we shall use later to configure the adapter of our RecyclerView widget. When you pass the query instance to the setQuery() method of its builder, make sure you specify that the results returned are in the form of WeightEntry objects. The following code shows you how to do so:

You might have noticed that we are making our current activity the lifecycle owner of the FirestoreRecyclerOptions object. Doing this is important because we want our adapter to respond appropriately to common lifecycle events, such as the user opening or closing the app.

At this point we can create a FirestoreRecyclerAdapter object, which uses the FirestoreRecyclerOptions object to configure itself. Because the FirestoreRecyclerAdapter class is abstract, Android Studio should automatically override its methods to generate code that looks like this:

As you can see, the FirestoreRecyclerAdapter class is very similar to the RecyclerView.Adapter class. In fact, it is derived from it. That means you can use it the same way you would use the RecyclerView.Adapter class.

Inside the onCreateViewHolder() method, all you need to do is inflate the weight_entry.xml layout file and return a WeightEntryVH view holder object based on it.

And inside the onBindViewHolder() method, you must use the model argument to update the contents of the TextView widgets that are present inside the view holder.

While updating the weightView widget is straightforward, updating the timeView widget is slightly complicated because we don't want to show the timestamp, which is in milliseconds, to the user directly.

The easiest way to convert the timestamp into a readable date and time is to use the formatDateTime() method of the DateUtils class. In addition to the timestamp, the method can accept several different flags, which it will use to format the date and time. You are free to use flags that match your preferences.

Finally, don't forget to point the RecyclerView widget to the adapter we just created.

The app is ready. You should now be able to add new entries and see them appear in the list almost immediately. If you run the app on another device having the same Google account, you will see the same weight entries appear on it too.

Weight entries shown as a list

Conclusion

In this tutorial, you saw how fast and easy it is to create a fully functional weight tracker app for Android using Cloud Firestore as a database. Feel free to add more functionality to it! I also suggest you try publishing it on Google Play. With the Firebase Spark plan, which currently offers 1 GB of data storage for free, you will have no problems serving at least a few thousand users.

And while you're here, check out some of our other posts about Android app development!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.