Dominik Grzelak blog

In this tutorial I'll explain how to write and read data in an android application with Firebase. As case study we will implement an online highscore for an arbitrary game. We want to have a game where highscores are saved into the firebase database. So the application can show the latest highscores from users all over the world when visiting the highscore activity.

The Firebase Database is a key-value store where each pair is stored at paths in the database tree. The limitations are listed at this link.

Set up Firebase Realtime Database for Android

But first the setup. If you are using Android Studio it is a matter of some clicks. But you can also follow the instructions on the android developer page here to manually set up your android project.

Database set up

So go to your firebase console and select your application and switch to database. We dont need to explicitly create a key in the firebase console because it will be added automatically when we write data to a new child key. The key we're going to use has the name

highscores

After that we have to define some rules for the access.

Important: in production stage dont use these rules!
For development and in the course of this article we make it easy and allow everyone to write and read the database:

// These rules give anyone, even people who are not users of your app,
// read and write access to your database
{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Example Scenario: A Game with Online Highscore

We want to implement a game where highscores are saved into the firebase database. So the application can show the
latest highscores from users all over the world.

HighscoreController

First, we need to write data before we can show an highscore list. The HighscoreController class will have all the logic to write and read highscore data.

We have a class which is managing all save and reads of the highscores. It will take any object of the
interface Highscore:

interface Highscore {
	String getPlayerId();
	long getHighscore();
}

The implementation could look like this

public class PlayerScoreDTO implements Highscore {

	private String playerid;
	private long highscore;
	public PlayerScoreImpl(String playerId, long highscore) {
		this.player = playerId;
		this.highscore = highscore;
	}
	
	public String getPlayerId() {
		return this.playerid;
	}
	
	public long getHighscore() {
		return this.highscore;
	}
}

It's implemented as a DataTransferObject and only carries the necessary information to write the highscore of a player to the database. In other word it's a POJO (Plain Old Java Object) which has only minimal getter/setter and attributes for the highscore data. It takes the actual id of an player object and saves it into its private member variable playerid. Same with the highscore. Imagine your player object would carry much more information about power, items, options or even behaviour logic. It would make no sense to save the whole reference or a copy if it and carry it the whole time around.

So the HighscoreController looks like this and is implemented as a Singleton. It will manage all read and write operations of highscore objects:

public class HighscoreController {

	DatabaseReference myRef;
	private static HighscoreController instance = new HighscoreController();
    private HighscoreController() { 
		FirebaseDatabase database = FirebaseDatabase.getInstance();
        myRef = database.getReference();
	}
	
    public static synchronized HighscoreController getInstance() {
        return instance;
    }
}

Lets stop here for now. The HighscoreController as Singleton can now be invoked from any class in the android project having the same state everywhere.

The important things happen in the constructor: the FirebaseDatabase is instantiated and a reference to it is saved in the member variable myRef of type DatabaseReference. From there we can move down the "database tree". The URI of the database can be found in the google-services.json under the key

"firebase_url": "https://YOUR-APP-NAME.firebaseio.com"

You could also specify the URI directly in the getReference() method but in this case the class is clever enough to read all relevant information of our project.

Write Data

For the write operation we will implement the method writeHighscores for the HighscoreController class. It takes a Highscore object as argument:

public class HighscoreController {
	
	//previous code 
	
	public void saveHighscores(Highscore highscore) {
		myRef.child("highscore").child(each.getPlayerId()).setValue(each.getHighscore());
		//myRef.child("highscores").setValue(highscore);
	}

}

Thats all: myRef points to the root of our database. We go down to the child node "highscore" and add a new player highscore. For that a new child node is created with playerid as key and the highscore as value. Only the necessary values are transfered to the database service. Thats the reason why we're using the Highscore interface. The HighscoreController is also decoupled from the real player profile and only need to know whats needed to write the highscore. If the method is called again the value is updated. Thats a service.

You cannot write every data structure to the firebase database. The native values which can be written with the setValue methods are:

  • Boolean
  • Long
  • Double
  • Map<String, Object>
  • List<Object>

Keys always has to be Strings speaking of child nodes when we're using the child() method.

Delete Data

If you want to delete a data set simply set the value at the specified key to null:

myRef.child("highscore").child(each.getPlayerId()).setValue(null);

So the highscore entry for player with id "getPlayerId()" will be deleted.

Read data

This part is different from other read operations you may encountered. For that we implement the interface ValueEventListener:

public class HighscoreController implements ValueEventListener {   

	//previous code
	
	@Override
    public void onDataChange(DataSnapshot dataSnapshot) {

    }

    @Override
    public void onCancelled(DatabaseError databaseError) {

    }
}

which gives us two new methods to fill with code. The methods are called if changes occur at the specified location, in this case
at the node "highscore" in the firebase database. With that you get live updates directly into your android application.

We will add a new member variable which stores the latest results retrieved from the database:

public class HighscoreController implements ValueEventListener {  
	List highscores = new ArrayList<>();

	private HighscoreController() { 
		FirebaseDatabase database = FirebaseDatabase.getInstance();
        myRef = database.getReference(); //"users"
        myRef.child("highscores").addValueEventListener(this);
	}
	
	public List getHighscores() {
		return highscores;
	}

	// previous code
}

We also add the ValueEventListener. The argument is the class itself because we implemented the interface ValueEventListener. From the we can listen to the latest updates from the database. Now we can update our onDataChange-method:

public class HighscoreController implements ValueEventListener {   

	//previous code
	
	@Override
    public void onDataChange(DataSnapshot dataSnapshot) {			
		highscores.clear();
		Iterable children = dataSnapshot.getChildren();
        for(DataSnapshot each: children) {
			highscores.add(new PlayerScoreDTO(each.getKey(),(Long)each.getValue()));
		}
    }
	
	//previous code
}

Thats all. Every time the method is called the list is emptied before the new data is read. Here you can implement different behaviours:

  • only load new data if a specific time has passed: check a time variable
  • only load new data if user pressed a button before: set a flag and check it in the method
  • load data once - only fill list if the size of the highscore is zero
     

Show highscores

In any activity call your HighscoreController class like this:

HighscoreController conroller = HighscoreController.getInstance();

List highscores = conroller.getHighscores();

//highscores are loaded hopefully

Keep in mind that it can take some seconds before the methods of the listener class are called. You should display a loading animation and check periodically if the conroller.getHighscores() is still zero. After a too long time you can interrupt the wait if no data is available.

Problems

Writing and reading data is not that hard with firebase.
But many other things can go wrong. What does happen if you havn't internet connection? What do you do if the database key is not available? Is the object you retrieving really an instance of Highscore, String, Long etc.
You have to take care of all these and more scenarios before you put you application into production stage. And dont forget
to implement an user login and change the database rules to:

// These rules require authentication
{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

See the documentation for further reference.