Android working with Firebase Realtime Database

Firebase Realtime database is a cloud hosted database that supports multiple platforms Android, iOS and Web. All the data is stored in JSON format and any changes in data, reflects immediately by performing a sync across all the platforms & devices. This allows us to build more flexible realtime apps easily with minimal effort.

This article covers basics integration of firebase realtime database. The other concepts like performs CRUD operations, data validations, firebase access rules also covered. If you are new to firebase, I suggest you check my other articles about Firebase Auth and Firebase Analytics to improve your knowledge over firebase.

firebase-realtime-database-android

1. How the Data is Stored – JSON Structured

Firebase realtime database is a schemaless database in which the data is stored in JSON format. Basically the entire database is a big JSON tree with multiple nodes. So when you plan your database, you need to prepare the json structure in way that the data is accessible in easier way by avoiding nesting of child nodes.

Here is an example of storing list of user profiles and posts in json tree. You can go through firebase Structure Your Database guide to learn the best practises while defining the database structure.

{
  "users": [
    {
      "name": "Ravi Tamada",
      "email": "ravi@androidhive.info",
      "address": "XXX, XXXX, 1234"
    }
  ],
  "posts": [
    {
      "id": 100,
      "author": "Ravi Tamada",
      "content": "This is awesome firebase realtime database...",
      "timestamp": "13892733894"
    }
  ]
}

2. Offline Data

Firebase provides great support when comes to offline data. It automatically stores the data offline when there is no internet connection. When the device connects to internet, all the data will be pushed to realtime database. However enabling disk persistence stores the data offline even though app restarts. Disk persistence can be enabled by calling below one line code. Here is complete guide about firebase offline capabilities.

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

3. Performing CRUD Operations

Before getting into the android app, I would like to give you basic information about performing CRUD operations on to realtime database. Later we’ll combine all these concepts together to build a simple app with firebase realtime database as backend.

In order to perform any operation on to database whether it can be read or write, you need to get the reference to database first. The below code gives you reference to database JSON top node. From here you need to use the child node names to traverse further.

private DatabaseReference mDatabase;

mDatabase = FirebaseDatabase.getInstance().getReference();

3.1 Inserting Data

To insert data, you can use setValue() method on to database reference path. This will create or update the value on path provided. For an example below code inserts a node called “copyright” in json top level.

DatabaseReference mRef = mDatabase.getReference("copyright");

mRef.setValue("©2016 androidhive. All rights Reserved");

The realtime database accepts multiple data types String, Long, Double, Boolean, Map<String, Object>, List<Object> to store the data. You can also use custom java objects to store the data which is very helpful when storing model class directly in database.

Let’s say you want to store user profile in the database. First you need to create User model with an empty constructor and other properties.

@IgnoreExtraProperties
public class User {

    public String name;
    public String email;

    // Default constructor required for calls to
    // DataSnapshot.getValue(User.class)
    public User() {
    }

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

As every user needs a unique Id, you can generate one by calling push() method which creates an empty node with unique key. Then get the reference to ‘users’ node using child() method. Finally use setValue() method to store the user data.

DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference("users");

// Creating new user node, which returns the unique key value
// new user node would be /users/$userid/
String userId = mDatabase.push().getKey();

// creating user object
User user = new User("Ravi Tamada", "ravi@androidhive.info");

// pushing user to 'users' node using the userId
mDatabase.child(userId).setValue(user);

By running the above code, a new user node will be inserted in database with a unique key value. In general, the user id should be acquired by implementing Firebase Auth in your app which gives you authId that acts as user id.

{
  "users": [
    "-KTYWvZG4Qn9ZYTc47O6" : {
      "email" : "ravi@androidhive.info",
      "name" : "Ravi Tamada"
    },
    {
      ...
    }
  ]
}

3.2 Reading Data

To read the data, you need to attach the ValueEventListener() to the database reference. This event will be triggered whenever there is a change in data in realtime. In onDataChange() you can perform the desired operations onto new data.

Below is the event listener that is triggered whenever there is a change in user profile data that we created earlier.

mDatabase.child(userId).addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {

        User user = dataSnapshot.getValue(User.class);

        Log.d(TAG, "User name: " + user.getName() + ", email " + user.getEmail());
    }

    @Override
    public void onCancelled(DatabaseError error) {
        // Failed to read value
        Log.w(TAG, "Failed to read value.", error.toException());
    }
});

3.3 Updating Data

To update data, you can use the same setValue() method by passing new value. You can also use updateChildren() by passing the path to update data without disturbing other child nodes data.

For example if you want to update only the user email, you can use below code block.

String newEmail = 'androidhive@gmail.com';

mDatabase.child(userId).child("email").setValue(newEmail);

3.4 Deleting Data

To delete data, you can simply call removeValue() method on to database reference. You can also pass null to setValue() method which do the same delete operation.

You can learn more about performing CRUD operations onto more advanced data like Lists of data here.

4. Security & Rules

Firebase rules provides a way to identify user role while performing read and write operations. These rules will acts a security layer on the server before perform any CRUD operation. By default the rules allows user to perform read & write operation only after authentication.

The below rules allow authenticated users only to read or write data.

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

Below rules allows everyone to read & write data without authentication.

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

You can also use these rules to validate data before inserting into database. For example below rules validates the name to be less than 50 chars and email to be valid using email regular expression.

{
	"rules": {
		".read": true,
		".write": true,
		"users": {
			"$user": {
				"name": {
					".validate": "newData.isString() && newData.val().length < 50"
				},
				"email": {
					".validate": "newData.isString() && newData.val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i)"
				}
			}
		}
	}
}

Go through firebase security & rules guide to learn more about the security concepts.

Now we have enough knowledge to get started with an android project. Let’s create one and see how to integrate the realtime database with an example app.

5. Creating Android Project

1. First thing you need to do is go to https://firebase.google.com/ and make an account to gain access to their console. After you gain access to the console you can start by creating your first project.

2. Give the package name of your project (mine is info.androidhive.firebase) in which you are going to integrate the Firebase. Here the google-services.json file will be downloaded when you press add app button.

android-creating-firebase-app

3. Create a new project in Android Studio from File ⇒ New Project. While filling the project details, use the same package name which you gave in firebase console. In my case I am using same info.androidhive.firebase.

4. Paste the google-services.json file to your project’s app folder. This step is very important as your project won’t build without this file.

5. Now open the build.gradle located in project’s home directory and add google playstore dependency.

dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        classpath 'com.google.gms:google-services:3.0.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

6. Open app/build.gradle and add firebase database dependency. At the very bottom of the file, add apply plugin: ‘com.google.gms.google-services’

dependencies {
    // Adding support library for this demo app
    compile 'com.android.support:design:24.2.1'

    compile 'com.google.firebase:firebase-database:9.6.1'
}

apply plugin: 'com.google.gms.google-services'

7. In order to store user profile, we need a model class called User.java. Create a class named User.java and add below class properties. If You want you can add few more properties like address, mobile etc.,

package info.androidhive.firebase;

import com.google.firebase.database.IgnoreExtraProperties;

/**
 * Created by Ravi Tamada on 07/10/16.
 * www.androidhive.info
 */

@IgnoreExtraProperties
public class User {

    public String name;
    public String email;

    // Default constructor required for calls to
    // DataSnapshot.getValue(User.class)
    public User() {
    }

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

8. Open the layout file of main activity activity_main.xml and add the below layout. This layout creates a simple form where you can enter the profile data to store in database.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="info.androidhive.firebase.MainActivity">

    <TextView
        android:id="@+id/txt_user"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_horizontal_margin"
        android:textSize="20dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/name"
                android:inputType="textCapWords"
                android:maxLines="1" />

        </android.support.design.widget.TextInputLayout>

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/email"
                android:inputType="textEmailAddress"
                android:maxLines="1" />

        </android.support.design.widget.TextInputLayout>

        <Button
            android:id="@+id/btn_save"
            style="?android:textAppearanceSmall"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:background="@color/colorPrimary"
            android:text="@string/action_save"
            android:textColor="@android:color/white"
            android:textStyle="bold" />

    </LinearLayout>

</LinearLayout>

9. Open MainActivity.java and do the below necessary changes. The code is very simple and easily understandable.

Our goal is to create the json structure as below in which ‘app_title’ stores the app title. ‘users’ stores user profiles as an array of nodes.

{
  "app_title" : "Realtime Database",
  "users" : {
    "-KTYWvZG4Qn9ZYTc47O6" : {
      "email" : "ravi@androidhive.info",
      "name" : "Ravi Tamada"
    }
  }
}

> getReference(“app_title”) create a node named app_title which stores the toolbar title.

> getReference(“users”) gets reference to users node.

> createUser() method stores a new user in realtime database

> updateUser() method updates user information like name and email.

package info.androidhive.firebase;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private TextView txtDetails;
    private EditText inputName, inputEmail;
    private Button btnSave;
    private DatabaseReference mFirebaseDatabase;
    private FirebaseDatabase mFirebaseInstance;

    private String userId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Displaying toolbar icon
        getSupportActionBar().setDisplayShowHomeEnabled(true);
        getSupportActionBar().setIcon(R.mipmap.ic_launcher);

        txtDetails = (TextView) findViewById(R.id.txt_user);
        inputName = (EditText) findViewById(R.id.name);
        inputEmail = (EditText) findViewById(R.id.email);
        btnSave = (Button) findViewById(R.id.btn_save);

        mFirebaseInstance = FirebaseDatabase.getInstance();

        // get reference to 'users' node
        mFirebaseDatabase = mFirebaseInstance.getReference("users");

        // store app title to 'app_title' node
        mFirebaseInstance.getReference("app_title").setValue("Realtime Database");

        // app_title change listener
        mFirebaseInstance.getReference("app_title").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Log.e(TAG, "App title updated");

                String appTitle = dataSnapshot.getValue(String.class);

                // update toolbar title
                getSupportActionBar().setTitle(appTitle);
            }

            @Override
            public void onCancelled(DatabaseError error) {
                // Failed to read value
                Log.e(TAG, "Failed to read app title value.", error.toException());
            }
        });

        // Save / update the user
        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String name = inputName.getText().toString();
                String email = inputEmail.getText().toString();

                // Check for already existed userId
                if (TextUtils.isEmpty(userId)) {
                    createUser(name, email);
                } else {
                    updateUser(name, email);
                }
            }
        });

        toggleButton();
    }

    // Changing button text
    private void toggleButton() {
        if (TextUtils.isEmpty(userId)) {
            btnSave.setText("Save");
        } else {
            btnSave.setText("Update");
        }
    }

    /**
     * Creating new user node under 'users'
     */
    private void createUser(String name, String email) {
        // TODO
        // In real apps this userId should be fetched
        // by implementing firebase auth
        if (TextUtils.isEmpty(userId)) {
            userId = mFirebaseDatabase.push().getKey();
        }

        User user = new User(name, email);

        mFirebaseDatabase.child(userId).setValue(user);

        addUserChangeListener();
    }

    /**
     * User data change listener
     */
    private void addUserChangeListener() {
        // User data change listener
        mFirebaseDatabase.child(userId).addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                User user = dataSnapshot.getValue(User.class);

                // Check for null
                if (user == null) {
                    Log.e(TAG, "User data is null!");
                    return;
                }

                Log.e(TAG, "User data is changed!" + user.name + ", " + user.email);

                // Display newly updated name and email
                txtDetails.setText(user.name + ", " + user.email);

                // clear edit text
                inputEmail.setText("");
                inputName.setText("");

                toggleButton();
            }

            @Override
            public void onCancelled(DatabaseError error) {
                // Failed to read value
                Log.e(TAG, "Failed to read user", error.toException());
            }
        });
    }

    private void updateUser(String name, String email) {
        // updating the user via child nodes
        if (!TextUtils.isEmpty(name))
            mFirebaseDatabase.child(userId).child("name").setValue(name);

        if (!TextUtils.isEmpty(email))
            mFirebaseDatabase.child(userId).child("email").setValue(email);
    }
}

Run & test the app once. You should be able to see the changes in realtime in your firebase console. Check the Demo video that shows how to run and test the app.

android-firebase-realtime-database

6. Pricing

Unlike Analytics, Cloud Messaging, Crash Reporting and other services, firebase realtime database is not completely free. There are certain limitations in Free plan. You need to pay few bucks for the usage of number of connections, disk usage and network usage. For more information check out the firebase pricing plans.

Here is the quick overview of realtime database pricing details (as of Oct 12th, 2016)

Realtime Database Pricing

 Free$25 per monthPay as you go
Simultaneous connections100UnlimitedUnlimited
GB stored1 GB2.5 GB$5/GB
GB downloaded10 GB20 GB$1/GB
Daily private backupsNoYesYes

7. References

Security rules:
https://firebase.google.com/docs/database/security/quickstart#sample-rules

Firebase Blog
https://firebase.googleblog.com/2016/07/have-you-met-realtime-database.html

Ravi is hardcore Android programmer and Android programming has been his passion since he compiled his first hello-world program. Solving real problems of Android developers through tutorials has always been interesting part for him.