EngEd Community

Section’s Engineering Education (EngEd) Program fosters a community of university students in Computer Science related fields of study to research and share topics that are relevant to engineers in the modern technology landscape. You can find more information and program guidelines in the GitHub repository. If you're currently enrolled in a Computer Science related field of study and are interested in participating in the program, please complete this form .

Integrating Cloud Firestore Database in Android using Kotlin

July 1, 2021

Firestore is a cloud-hosted NoSQL database that can be integrated into various platforms (iOS, Android, and web apps) via the respective SDK(s).

It is a reliable database because it offers flexible data storage structures, expressive querying, offline caching support, improved identity and access management (IAM), and synchronized real-time updates.

Modern apps are expected to run smoothly and offer the best possible user experience and that’s exactly what Firestore brings us. In this article, we’ll learn how Firestore can be used in an Android app using the Kotlin programming language.

Prerequisites

To follow through this article, you’ll need to have a basic experience in:

Table of contents

How Firestore stores data

Unlike other popular databases, Firestore stores data in a NoSQL format, that is, data is stored in JSON objects within nodes. These nodes are referred to as documents. A document can not exist on its own. There must be a parent container that holds at least one document. This container is referred to as a collection.

As the database grows, a tree-like structure is formed with the related documents and collections connected to a common collection with the aid of their respective IDs. Firestore is unique in that it supports creation of collections inside documents. These buried collections are known as sub-collections.

For instance, to create a users database, we’ll create a collection called users and add documents with unique IDs each holding the user’s attributes. These attributes can be of the following data types:

Boolean, Number, String, Geo point, Binary blob, Cloud Firestore references, Arrays, Map values, and Timestamp.

Create a Firebase project

Cloud Firestore is among the products that Firebase offers. We, therefore, need to create a Firebase project in which we’ll create the database. Go to the Official Firebase website and log in using your Google account.

Click Go to console and tap + add project. This initiates a 3-step process that asks you to set the name for your project, project ID, location, and finally to accept the terms of service.

Firebase project example

Create an Android project

Now, let’s go ahead and create an Android project that we’ll later connect with the Firebase project we just created. Launch your IDE and start a new project of your choice.

Connect the two projects

So far we’ve created two distinct projects but they both need each other to serve their purpose. To connect Android with Firebase project you’ll need to do the following;

Download project configurations file from Firebase console

A configuration file is a JSON file usually named google-services.json that contains information about all apps connected to a Firebase project.

Open Firebase console and tap the project that you want to connect to your App. Just below the project’s name is a list of available platforms that you can use. Click the Android logo to proceed.

1. Add Application details

This is the most critical step as it directly takes effect on the configuration file. Copy and paste the package name to avoid typing mistakes. This can be located in the module level build.gradle file.

Add app details

2. Download config file

After registering the app, click Download google.services.json to get your configuration file.

Download config file

Head back to Android Studio and switch to Project view. This shows all files and directories in your project. Paste the config file into the app folder and sync.

Load Firebase SDK into our App

Firebase SDK allows us to access services such as Firebase auth, Realtime database, Firebase ML, Firestore among others.

1. Firestore dependencies

Add the following dependency in the module-level build.gradle file:

// BoM and Firestore

implementation platform('com.google.firebase:firebase-bom:28.0.1')
implementation 'com.google.firebase:firebase-firestore-ktx'

This declares Firestore library dependency. BoM (Bill of Materials) is a library that allows us to use Firebase libraries without specifying their versions.

Include this plugin too:

plugins{
    id 'com.google.gms.google-services'
}
2. Add classpaths (top level build.gradle file)
dependencies{
    classpath 'com.google.gms:google-services:4.3.8'
}

Don’t forget to add internet permission in the Manifest file:

<uses-permission android:name="android.permission.INTERNET"/>

Sync and wait for the gradle build to complete.

We’ve successfully connected Firebase project to our Android app. Open Firebase console and you should see something similar to this. Notice the project has a little Android icon at the bottom indicating that it has been connected to Android platform.

Firebase projects

Create Cloud Firestore database

Now that the setup is complete, we can proceed to create a Cloud Firestore database by clicking the respective project » Firestore database on the left panel » Create database as shown below.

Create databse

Firestore Data Security

Firestore uses a variety of rules to control database access. The following are the two commonly used rules.

1. Test rule

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.time < timestamp.date(2021, 7, 12);
    }
  }
}

This allows all app users to read, write and perform any other data manipulation activity on the database for one month if rules remain unchanged. After this, all users are denied access to the database. Therefore this rule is not recommended for production, but it is preferred for testing purposes.

2. Authentication required

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

Unlike the test rule, this only allows authenticated users to access the database. For a general-level production app, this is the rule to go to.

To learn more about Firebase rules, be sure to check out Firebase Docs.

Building the Application User interface

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnUploadData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="8dp"
        app:layout_constraintVertical_bias="0.85"
        android:text="@string/upload_data"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnReadData"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/retrieve_data"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/btnUploadData"
        app:layout_constraintStart_toStartOf="@+id/btnUploadData"
        app:layout_constraintTop_toBottomOf="@+id/btnUploadData" />
</androidx.constraintlayout.widget.ConstraintLayout>

This creates two buttons. They’ll be used to trigger an upload and read data tasks respectively. Add the missing strings in the strings.xml file:

<resources>
    <string name="upload_data">Upload data</string>
    <string name="retrieve_data">Retrieve data</string>
</resources>

Enable viewBinding

View binding allows us to access views via the binding class of the XML layout they belong to. Add the code block below in the module level gradle file and sync your project:

android{
    ...

    buildFeatures{
        viewBinding true
    }
}

Inflate the UI

const val TAG = "FIRESTORE"

class MainActivity : AppCompatActivity() {
    private var binding: ActivityMainBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding?.root)

        // we'll call functions here
    }

Here, we’ve made use of viewBinding to inflate UI accordingly. We’ve also declared a global constant variable that will be used as a tag in our log messages.

Database operations

Using test rules. Operations in a database include Create, Read, Update, and Delete also known as CRUD operations.

Create database instance

Create a Kotlin class named FirebaseUtils and paste the code below in it:

class FirebaseUtils {
        val fireStoreDatabase = FirebaseFirestore.getInstance()
}

This serves as an API that allows us to add, get, delete, and update collections and documents in the database.

1. Upload data

An upload action will be initiated when the upload button is clicked. Paste the following in the MainActivity.kt file:

private fun uploadData() {
    binding!!.btnUploadData.setOnClickListener {

        // create a dummy data
        val hashMap = hashMapOf<String, Any>(
            "name" to "John doe",
            "city" to "Nairobi",
            "age" to 24
        )

        // use the add() method to create a document inside users collection
        FirebaseUtils().fireStoreDatabase.collection("users")
            .add(hashMap)
            .addOnSuccessListener {
                Log.d(TAG, "Added document with ID ${it.id}")
            }
            .addOnFailureListener { exception ->
                Log.w(TAG, "Error adding document $exception")
            }
    }

Call this function in the onCreate() method:

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    
    uploadData()
}

This adds a sample data and Logs the id of the document if the task was successful. Otherwise, an exception is thrown and logged as well. Exceptions can occur when we try to upload without an internet connection and/or permission or if the database rules don’t allow us to do so.

To confirm that the database is up and running, open it on the Firebase console and you should see something like this.

Saved data

Multiple numbers of documents are created based on the number of times we upload. These documents belong to the users collection and that’s why we have only one collection in our database.

2. Read data
private fun readData{
    binding!!.btnReadData.setOnClickListener {
            FirebaseUtils().fireStoreDatabase.collection("users")
                .get()
                .addOnSuccessListener { querySnapshot ->
                    querySnapshot.forEach { document ->
                        Log.d(TAG, "Read document with ID ${document.id}")
                    }
                }
                .addOnFailureListener { exception ->
                    Log.w(TAG, "Error getting documents $exception")
                }
        }
}

Here we’ve used the get() method to retrieve all documents in the users collection. Open the Logcat to check if the data is being retrieved successfully or not.

Logs

Conclusion

That’s it! You can now implement Firestore cloud database in your Android app. This can be advanced further to get input data from the user and upload/retrieve it using Kotlin coroutines for a smooth performance.

In the next tutorial, we’ll look at data modeling and querying techniques and display the actual data in a Recyclerview. The source code for this project can be found on this Github repository.

Happy Coding!


Peer Review Contributions by: Peter Kayere