Firestore on Android (Kotlin)

March 7, 2019

What is Firestore and why use Firestore?

  • NoSQL
  • Hosted, serverless with autoscaling.
  • You are billed by number of operations (read, write, delete), database storage and network bandwidth.
  • Realtime updates: auto synchronization across devices and with backend.
  • Offline support on client.
  • Mobile/Web Client Library: Android, iOS, Web
  • Server Client Library: C#, Go, Java, Node.js, PHP, Python, or Ruby

Imagine a database server which client (web, Android, iOS) can access directly, with security rules and read/write/delete operation can trigger cloud functions (imagine stored procedure for RDBMS).

You can have a backend database server without a typical backend (thus no backend coding is required).

For authentication, you can use Firebase Authentication.

NOTE: Firestore introduction video

Firestore vs Datastore

Google Cloud Datastore is a server-side NoSQL database (cannot be accessed from Web/Frontend JavaScript, iOS or Android), thus no realtime updates/auto synchronization/offline feature.

The lastest version of Datastore is also known as Cloud Firestore Datastore Mode. Important new feature is strong consistency.

Firestore is capable of partial update, which Datastore doesn’t support.

Cloud Firestore Native Mode is commonly known as Firestore.

The previous database on Firebase is known as Firebase Realtime Database

NOTE: Cloud Firestore Native Mode vs Datastore Mode, Google Cloud Datastore vs Firestore

NOTE: Cloud Firestore or Realtime Database

Enable Firestore

Goto Firebase Console and create a new project (or import an existing Google Cloud project).

Select Database (from the left panel menu) and click Create Database.

NOTE: Enabling Cloud Firestore will preclude you from using Cloud Datastore with this project, notably from the associated App Engine app. Each project can only use Firestore (Cloud Firestore Native Mod) or Datastore (Cloud Firestore Datastore Mode), not both.

NOTE: Firestore become GA (out of Beta) since 31 Jan 2019.

When selecting Cloud Firestore Security Rules, I would recommend Locked mode (Test mode just seems too dangerous with your database access wide opened).

NOTE: Cloud Firestore Security Rules only apply to Mobile/Web Client Library: Android, iOS, Web. The server client libraries bypass all Cloud Firestore Security Rules and instead authenticate through Google Application Default Credentials. If you are using the server client libraries or the REST or RPC APIs, make sure to set up Cloud Identity and Access Management for Cloud Firestore.

Setup Android Environment

Add firebase to Android.

Add Firestore dependencies to Module:app build.gradle.

dependencies {
    // https://firebase.google.com/support/release-notes/android#latest_sdk_versions
    implementation 'com.google.firebase:firebase-firestore:18.0.1'
}

Initialize Firestore.

val db = FirebaseFirestore.getInstance()

I bump into java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.luasoftware.nuanxindan. Make sure to call FirebaseApp.initializeApp(Context) first. error when trying to run the app. I need to use google-services:4.2.0.

Edit Project build.gradle.

buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.2.0'
    }
}

Add Data

Write some code to add data to collection test.

val db = FirebaseFirestore.getInstance()
val item = mapOf(
    "name" to "Desmond",
    "born" to 1979,
    "active" to true
)

db.collection("test")
    .add(item)
    .addOnSuccessListener { doc -> 
        Timber.d("Add success: ${doc.id}")
    }
    .addOnFailureListener { e -> 
        Timber.w("Add fail", e)
    }

NOTE: Collection (test) is like a Table, while Document (item) is like a Record/Row

When you run the code, you will probably run into com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.. This is expected as we previously select Locked mode when we create the database.

NOTE: addOnSuccessListener and addOnFailureListener would not be called if the backend could not be reached (when no network access).

Security Rules

Edit the Firestore Security Rules: Firebase Console -> Database -> Rules.

Edit the rules to allow read/write access to test collection.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
    
    match /test/{testId} {
      allow read: if true;
      allow write: if true;
    }
  }
}

You can test the rule by using the Simulator (on the bottom left corner of the rule).

Click Publish and the new rule will take 1 minute to take effect.

Try to run the add data code again.

Read Data

db.collection("test")
    .get()
    .addOnSuccessListener { result -> 
        for (document in result) {
            Timber.d("${document.id}: ${document["name"]}")
        }
    }
    .addOnFailureListener { e ->
        Timber.w("Add fail", e)
    }

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.