Firebase Realtime Database – Display List of Data with FirebaseRecyclerAdapter | Android

In previous post, we had known how to read/write list of data object. Today, we’re gonna look at way to display List of Data in an Android App with FirebaseUI FirebaseRecyclerAdapter.

Related Articles:
Firebase Realtime Database – Read/Write Data example | Android
Firebase Realtime Database – Get List of Data example | Android

I. FirebaseUI Database

To use the FirebaseUI to display list of data, we need:
– Java class for data object (Model)
– Java class for holding UI elements that match with Model’s fields (ViewHolder and layout)
– Custom RecyclerView adapter to map from a collection from Firebase to Android (FirebaseRecyclerAdapter)
RecyclerView object to set the adapter to provide child views on demand.

firebase-realtime-db-firebase-app-demo

1. Model and ViewHolder

Model class is a class that represents the data from Firebase:

public class Message {

    public String author;
    public String body;
    public String time;

    // ...
}

ViewHolder layout (R.layout.item_message) with UI items that correspond to Model fields:




    

    

    

ViewHolder class contains Android UI fields that point to layout items:

public class MessageViewHolder extends RecyclerView.ViewHolder{
    public TextView authorView;
    public TextView timeView;
    public TextView bodyView;

    public MessageViewHolder(View itemView) {
        super(itemView);

        authorView = (TextView) itemView.findViewById(R.id.tv_author);
        timeView = (TextView) itemView.findViewById(R.id.tv_time);
        bodyView = (TextView) itemView.findViewById(R.id.tv_body);
    }
}
2. FirebaseRecyclerAdapter subclass

We need a subclass of the FirebaseRecyclerAdapter and implement its populateViewHolder() method:

private FirebaseRecyclerAdapter mAdapter;
// ...
// mMessageReference = FirebaseDatabase.getInstance().getReference("messages");

Query query = mMessageReference.limitToLast(8);

mAdapter = new FirebaseRecyclerAdapter(
                Message.class, R.layout.item_message, MessageViewHolder.class, query) {
    @Override
    protected void populateViewHolder(MessageViewHolder viewHolder, Message model, int position) {
        viewHolder.authorView.setText(model.author);
        viewHolder.timeView.setText(model.time);
        viewHolder.bodyView.setText(model.body);
    }

    @Override
    public void onChildChanged(EventType type, DataSnapshot snapshot, int index, int oldIndex) {
       super.onChildChanged(type, snapshot, index, oldIndex);

        rcvListMessage.scrollToPosition(index);
    }
};

Now look at these lines of code:

Query query = mMessageReference.limitToLast(8);

mAdapter = new FirebaseRecyclerAdapter(
                Message.class, R.layout.item_message, MessageViewHolder.class, query)

– We tell FirebaseRecyclerAdapter object to use Message.class when reading from the database.
– Each Message will be displayed in a R.layout.item_message (that has 3 TextView elements: tv_author, tv_time, tv_body).
– We indicate class for ViewHolder
– We can just give reference to database node or sort/filter data by using Query:

Query query = mMessageReference;

Query query = mMessageReference.orderByKey();
// orderByValue() or orderByChild("...")

Query query = mMessageReference.limitToLast(8);
// limitToFirst(..), startAt(...), endAt(...), equalTo(...)

FirebaseRecyclerAdapter will call populateViewHolder() method for each Model it finds in database. It passes us the Model and a ViewHolder.
So what we should do is map the fields from model to the correct TextView items:

@Override
protected void populateViewHolder(MessageViewHolder viewHolder, Message model, int position) {
    viewHolder.authorView.setText(model.author);
    viewHolder.timeView.setText(model.time);
    viewHolder.bodyView.setText(model.body);
}

We can override onChildChanged() method to do something every time a Child in database has changed its value (add, update…).

3. RecyclerView

Now we set the adapter for RecyclerView object to provide child views on demand:

private RecyclerView rcvListMessage;
// ...

rcvListMessage = (RecyclerView) findViewById(R.id.rcv_list_message);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setReverseLayout(false);
rcvListMessage.setHasFixedSize(true);
rcvListMessage.setLayoutManager(layoutManager);

//mAdapter = new FirebaseRecyclerAdapter(...);
rcvListMessage.setAdapter(mAdapter);

Remember to call adapter cleanup() method to stop listening for changes in the Firebase database:

@Override
protected void onDestroy() {
    super.onDestroy();

    mAdapter.cleanup();
}
4. Dependency

To add Support Library to Android project, open the build.gradle file (project-level).
Make sure that the repositories section includes a maven section with the “https://maven.google.com” endpoint:

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}

For build.gradle file (App-level)

dependencies {
    // ...
    compile 'com.android.support:appcompat-v7:25.4.0'
    compile 'com.google.firebase:firebase-database:11.0.2'
    compile 'com.firebaseui:firebase-ui-database:2.1.1'
}

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

II. Practice

1. Goal

We will build an Android App that can:
– create Account, sign in/sign out for Firebase Authentication.
– read/write user to Firebase Realtime Database.
(2 lines above come from this Post).
– write Message item to 2 nodes (/messages/$key and /user-messages/$userid/$key) at the same time, then read list of all Message items. (from this Post)
– display list of Messages using FirebaseUI FirebaseRecyclerAdapter.

firebase-realtime-db-firebase-app-demo

2. Technology

– Gradle 2.3.3
– Android Studio 2.x
– Firebase Android SDK 11.x
– Firebase UI Database 2.1.1

3. Project Structure

firebase-realtime-db-firebase-ui-structure

LoginActivity is for Authentication, then user can enter MessageActivity to send Message to Firebase Realtime Database and show list of Message data.

4. Step by step
4.1 Create Android Project

– Generate new Android Project with package com.javasampleapproach.firebaserealtimedb.
build.gradle (project-level):

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'com.google.gms:google-services:3.1.0'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}

build.gradle (App-level):

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.0"
    defaultConfig {
        applicationId "com.javasampleapproach.firebaserealtimedb"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.4.0'
    compile 'com.google.firebase:firebase-auth:11.0.2'
    compile 'com.google.firebase:firebase-database:11.0.2'
    compile 'com.firebaseui:firebase-ui-database:2.1.1'
    testCompile 'junit:junit:4.12'
}

apply plugin: 'com.google.gms.google-services'
4.2 Create Firebase Project & Add Firebase Config file

– Follow this guide to generate google-services.json file and move it into your Android App root directory. You don’t need to have SHA-1 Key in this example, just leave it blank.

– Make sure that package_name in google-services.json has a correct value according to:
+ applicationId in build.gradle (App-level).
+ package in AndroidManifest.xml.
In this case, it is com.javasampleapproach.firebaserealtimedb.

firebase-realtime-db-add-app

4.3 Enable Firebase Auth

Go to Your Firebase Project Console -> Authentication -> SIGN-IN METHOD -> Enable Email/Password.

4.4 Model
package com.javasampleapproach.firebaserealtimedb.models;

import com.google.firebase.database.IgnoreExtraProperties;

@IgnoreExtraProperties
public class User {
    public String name;
    public String email;

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

    public User(String username, String email) {
        this.name = username;
        this.email = email;
    }
}
package com.javasampleapproach.firebaserealtimedb.models;

import com.google.firebase.database.Exclude;
import com.google.firebase.database.IgnoreExtraProperties;

import java.util.HashMap;
import java.util.Map;

@IgnoreExtraProperties
public class Message {

    public String author;
    public String body;
    public String time;

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

    public Message(String author, String body, String time) {
        this.author = author;
        this.body = body;
        this.time = time;
    }

    @Exclude
    public Map toMap() {
        HashMap result = new HashMap<>();
        result.put("author", author);
        result.put("body", body);
        result.put("time", time);

        return result;
    }
}
4.5 LoginActivity

To know how to implement Firebase Authentication App Client, please visit:
Firebase Authentication – How to Sign Up, Sign In, Sign Out, Verify Email | Android

In this tutorial, we don’t explain way to authenticate an user again.




    

        

        

        

    

    

        

            

            
        

        

            
package com.javasampleapproach.firebaserealtimedb;

import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.FirebaseDatabase;
import com.javasampleapproach.firebaserealtimedb.models.User;

public class LoginActivity extends AppCompatActivity implements
        View.OnClickListener {

    private static final String TAG = "LoginActivity";

    private TextView txtStatus;
    private TextView txtDetail;
    private EditText edtEmail;
    private EditText edtPassword;

    private FirebaseAuth mAuth;

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

        txtStatus = (TextView) findViewById(R.id.status);
        txtDetail = (TextView) findViewById(R.id.detail);
        edtEmail = (EditText) findViewById(R.id.edt_email);
        edtPassword = (EditText) findViewById(R.id.edt_password);

        findViewById(R.id.btn_email_sign_in).setOnClickListener(this);
        findViewById(R.id.btn_email_create_account).setOnClickListener(this);
        findViewById(R.id.btn_sign_out).setOnClickListener(this);
        findViewById(R.id.btn_test_message).setOnClickListener(this);

        mAuth = FirebaseAuth.getInstance();
    }

    @Override
    protected void onStart() {
        super.onStart();

        FirebaseUser currentUser = mAuth.getCurrentUser();
        updateUI(currentUser);
    }

    @Override
    public void onClick(View view) {
        int i = view.getId();

        if (i == R.id.btn_email_create_account) {
            createAccount(edtEmail.getText().toString(), edtPassword.getText().toString());
        } else if (i == R.id.btn_email_sign_in) {
            signIn(edtEmail.getText().toString(), edtPassword.getText().toString());
        } else if (i == R.id.btn_sign_out) {
            signOut();
        } else if (i == R.id.btn_test_message) {
            testMessage();
        }
    }

    private void createAccount(String email, String password) {
        Log.e(TAG, "createAccount:" + email);
        if (!validateForm(email, password)) {
            return;
        }

        mAuth.createUserWithEmailAndPassword(email, password)
                .addOnCompleteListener(this, new OnCompleteListener() {
                    @Override
                    public void onComplete(@NonNull Task task) {
                        if (task.isSuccessful()) {
                            Log.e(TAG, "createAccount: Success!");

                            // update UI with the signed-in user's information
                            FirebaseUser user = mAuth.getCurrentUser();
                            updateUI(user);
                            writeNewUser(user.getUid(), getUsernameFromEmail(user.getEmail()), user.getEmail());
                        } else {
                            Log.e(TAG, "createAccount: Fail!", task.getException());
                            Toast.makeText(LoginActivity.this, "Authentication failed!", Toast.LENGTH_SHORT).show();
                            updateUI(null);
                        }
                    }
                });
    }

    private void signIn(String email, String password) {
        Log.e(TAG, "signIn:" + email);
        if (!validateForm(email, password)) {
            return;
        }

        mAuth.signInWithEmailAndPassword(email, password)
                .addOnCompleteListener(this, new OnCompleteListener() {
                    @Override
                    public void onComplete(@NonNull Task task) {
                        if (task.isSuccessful()) {
                            Log.e(TAG, "signIn: Success!");

                            // update UI with the signed-in user's information
                            FirebaseUser user = mAuth.getCurrentUser();
                            updateUI(user);
                        } else {
                            Log.e(TAG, "signIn: Fail!", task.getException());
                            Toast.makeText(LoginActivity.this, "Authentication failed!", Toast.LENGTH_SHORT).show();
                            updateUI(null);
                        }

                        if (!task.isSuccessful()) {
                            txtStatus.setText("Authentication failed!");
                        }
                    }
                });
    }

    private void signOut() {
        mAuth.signOut();
        updateUI(null);
    }

    private void writeNewUser(String userId, String username, String email) {
        User user = new User(username, email);

        FirebaseDatabase.getInstance().getReference().child("users").child(userId).setValue(user);
    }

    private boolean validateForm(String email, String password) {

        if (TextUtils.isEmpty(email)) {
            Toast.makeText(LoginActivity.this, "Enter email address!", Toast.LENGTH_SHORT).show();
            return false;
        }

        if (TextUtils.isEmpty(password)) {
            Toast.makeText(LoginActivity.this, "Enter password!", Toast.LENGTH_SHORT).show();
            return false;
        }

        if (password.length() < 6) {
            Toast.makeText(LoginActivity.this, "Password too short, enter minimum 6 characters!", Toast.LENGTH_SHORT).show();
            return false;
        }

        return true;
    }

    private void updateUI(FirebaseUser user) {
        if (user != null) {
            txtStatus.setText("User Email: " + user.getEmail());
            txtDetail.setText("Firebase User ID: " + user.getUid());

            findViewById(R.id.email_password_buttons).setVisibility(View.GONE);
            findViewById(R.id.email_password_fields).setVisibility(View.GONE);
            findViewById(R.id.layout_signed_in_control).setVisibility(View.VISIBLE);

        } else {
            txtStatus.setText("Signed Out");
            txtDetail.setText(null);

            findViewById(R.id.email_password_buttons).setVisibility(View.VISIBLE);
            findViewById(R.id.email_password_fields).setVisibility(View.VISIBLE);
            findViewById(R.id.layout_signed_in_control).setVisibility(View.GONE);
        }
    }

    private String getUsernameFromEmail(String email) {
        if (email.contains("@")) {
            return email.split("@")[0];
        } else {
            return email;
        }
    }

    private void testMessage() {
        startActivity(new Intent(this, MessageActivity.class));
    }
}
4.6 ViewHolder



    

        

        

    

    

package com.javasampleapproach.firebaserealtimedb.viewholder;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;

import com.javasampleapproach.firebaserealtimedb.R;

public class MessageViewHolder extends RecyclerView.ViewHolder{
    public TextView authorView;
    public TextView timeView;
    public TextView bodyView;

    public MessageViewHolder(View itemView) {
        super(itemView);

        authorView = (TextView) itemView.findViewById(R.id.tv_author);
        timeView = (TextView) itemView.findViewById(R.id.tv_time);
        bodyView = (TextView) itemView.findViewById(R.id.tv_body);
    }
}
4.7 MessageActivity



    

        

        
    

    

    

        

        

            
package com.javasampleapproach.firebaserealtimedb;

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

import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.ChildEventListener;
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.Query;
import com.google.firebase.database.ValueEventListener;
import com.javasampleapproach.firebaserealtimedb.models.Message;
import com.javasampleapproach.firebaserealtimedb.models.User;
import com.javasampleapproach.firebaserealtimedb.viewholder.MessageViewHolder;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class MessageActivity extends AppCompatActivity {

    private static final String TAG = "MessageActivity";
    private static final String REQUIRED = "Required";

    private Button btnBack;
    private Button btnSend;
    private EditText edtSentText;
    private RecyclerView rcvListMessage;
    private FirebaseRecyclerAdapter mAdapter;

    private FirebaseUser user;

    private DatabaseReference mDatabase;
    private DatabaseReference mMessageReference;
    private ChildEventListener mMessageListener;

    private ArrayList messageList;

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

        btnSend = (Button) findViewById(R.id.btn_send);
        btnBack = (Button) findViewById(R.id.btn_back);
        edtSentText = (EditText) findViewById(R.id.edt_sent_text);
        rcvListMessage = (RecyclerView) findViewById(R.id.rcv_list_message);

        mDatabase = FirebaseDatabase.getInstance().getReference();
        mMessageReference = FirebaseDatabase.getInstance().getReference("messages");
        user = FirebaseAuth.getInstance().getCurrentUser();

        messageList = new ArrayList<>();

        btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                submitMessage();
                edtSentText.setText("");
            }
        });

        btnBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setReverseLayout(false);
        rcvListMessage.setHasFixedSize(true);
        rcvListMessage.setLayoutManager(layoutManager);

        Query query = mMessageReference.limitToLast(8);

        mAdapter = new FirebaseRecyclerAdapter(
                Message.class, R.layout.item_message, MessageViewHolder.class, query) {
            @Override
            protected void populateViewHolder(MessageViewHolder viewHolder, Message model, int position) {
                viewHolder.authorView.setText(model.author);
                viewHolder.timeView.setText(model.time);
                viewHolder.bodyView.setText(model.body);
            }

            @Override
            public void onChildChanged(EventType type, DataSnapshot snapshot, int index, int oldIndex) {
                super.onChildChanged(type, snapshot, index, oldIndex);

                rcvListMessage.scrollToPosition(index);
            }
        };

        rcvListMessage.setAdapter(mAdapter);
    }

    @Override
    protected void onStart() {
        super.onStart();

        ChildEventListener childEventListener = new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
                // A new message has been added
                // onChildAdded() will be called for each node at the first time
                Message message = dataSnapshot.getValue(Message.class);
                messageList.add(message);

                Log.e(TAG, "onChildAdded:" + message.body);
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
                Log.e(TAG, "onChildChanged:" + dataSnapshot.getKey());

                // A message has changed
                Message message = dataSnapshot.getValue(Message.class);
                Toast.makeText(MessageActivity.this, "onChildChanged: " + message.body, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                Log.e(TAG, "onChildRemoved:" + dataSnapshot.getKey());

                // A message has been removed
                Message message = dataSnapshot.getValue(Message.class);
                Toast.makeText(MessageActivity.this, "onChildRemoved: " + message.body, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
                Log.e(TAG, "onChildMoved:" + dataSnapshot.getKey());

                // A message has changed position
                Message message = dataSnapshot.getValue(Message.class);
                Toast.makeText(MessageActivity.this, "onChildMoved: " + message.body, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                Log.e(TAG, "postMessages:onCancelled", databaseError.toException());
                Toast.makeText(MessageActivity.this, "Failed to load Message.", Toast.LENGTH_SHORT).show();
            }
        };

        mMessageReference.addChildEventListener(childEventListener);

        // copy for removing at onStop()
        mMessageListener = childEventListener;
    }

    @Override
    protected void onStop() {
        super.onStop();

        if (mMessageListener != null) {
            mMessageReference.removeEventListener(mMessageListener);
        }

        for (Message message: messageList) {
            Log.e(TAG, "listItem: " + message.body);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        mAdapter.cleanup();
    }

    private void submitMessage() {
        final String body = edtSentText.getText().toString();

        if (TextUtils.isEmpty(body)) {
            edtSentText.setError(REQUIRED);
            return;
        }

        // User data change listener
        mDatabase.child("users").child(user.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                User user = dataSnapshot.getValue(User.class);

                if (user == null) {
                    Log.e(TAG, "onDataChange: User data is null!");
                    Toast.makeText(MessageActivity.this, "onDataChange: User data is null!", Toast.LENGTH_SHORT).show();
                    return;
                }

                writeNewMessage(body);
            }

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

    private void writeNewMessage(String body) {
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
        Message message = new Message(getUsernameFromEmail(user.getEmail()), body, time);

        Map messageValues = message.toMap();
        Map childUpdates = new HashMap<>();

        String key = mDatabase.child("messages").push().getKey();

        childUpdates.put("/messages/" + key, messageValues);
        childUpdates.put("/user-messages/" + user.getUid() + "/" + key, messageValues);

        mDatabase.updateChildren(childUpdates);
    }

    private String getUsernameFromEmail(String email) {
        if (email.contains("@")) {
            return email.split("@")[0];
        } else {
            return email;
        }
    }
}
4.8 Run & Check result

- Use Android Studio, build and Run your Android App:

firebase-realtime-db-firebase-app-demo

- Firebase Console:

III. Source code

FirebaseRealtimeDB-FirebaseRecylcerAdapter

2 thoughts on “Firebase Realtime Database – Display List of Data with FirebaseRecyclerAdapter | Android”

  1. Hi, I really enjoyed your tutorial, you taught a CRUD of update and retrive, but I have a question, how would I delete the messages?

  2. What i do not realize is actually how you’re not really much more well-liked than you might be right now. You’re so intelligent. You realize therefore considerably relating to this subject, produced me personally consider it from a lot of varied angles. Its like women and men aren’t fascinated unless it is one thing to do with Lady gaga! Your own stuffs outstanding. Always maintain it up!

Leave a Reply

Your email address will not be published. Required fields are marked *