Android Firebase ListView - Write, Read, Scroll to Last Added Item

May 24, 2018 Oclemy Android Firebase, Android ListView 25 minutes, 47 seconds

This is an android Firebase ListView Write, Read and Scroll to Last saved or added item position.

We insert or save data to firebase. After saving data, we automatically fetch or read data from the firebase.

Then populate a custom listview with cardviews. The CardViews have several textviews to display our data.

We then smooth scroll automatically to the last item in the listview.

This tutorial was requested by a community member in our Youtube Channel ProgrammingWizards TV.

We are Building a Vibrant YouTube Community

We have a fast rising YouTube Channel of friends. So far we've accumulated more than 2.6 million agreggate views and more than 10,000 subscribers. Here's the Channel: ProgrammingWizards TV.

Please go ahead subscribe(free obviously) as well. If you have a question or a comment you can post there instead of in this site.People are suggesting us tutorials to do there so you can too.

Here's this tutorial in Video Format.

Firebase Pre-requisites

To work with Firebase from android, you need the following:

  1. An android device/emulator running Android 4.0(Ice Cream Sandwich) or newer and Google Play Services 15.0.0 or higher.
  2. Android Studio. Prefer later versions.

1. Create Android Studio Application

Go over to android studio and create a project as usual. You can choose Empty Activity.

2. Create Firebase App in Firebase Console

Switch on your internet and go to Firebase Console in your browser. You'll need a Gmail account to login. First you need to create a Firebase App in the online Firebae Console.

Then Click Add Project.

Then Click Add App as below:

Firebase Add App.

Then choose Android as we are creating android app. Firebase Choose Android.

3. Register App

After creating a Firebase App you need to associate it with your android application. We call this registering your android app.

Firebase Register App.

You need to type the Android application's package name. You can get this in your app level build.gradle.

Then click next.

4. Download google-services.json

Next we download a google-services.json file which will contain our configurations.

It will be generated automatically. Just click download to download it.

Firebase Download google-services.json.

Let's now move to Android Studio

5. Copy google-services.json to Your App Folder

In your project, there is an app folder. Copy the downloaded google-services.json there.

You can do this in two ways. First navigate over to project files and just copy paste the file as below:

Paste google-services.json.

Alternatively, just move over to the folder via File Explorer and paste the file. Paste google-services.json.

We now need to add Firebase SDK to our android application.

So let's proceed to writing simple gradle statements.

Build.Gradle(Project Level)

(a). Add Google Services Class Path

Go to your open project level or root level build.gradle and specify the class path of google-services plugin.

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        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
    }
}

Please don't forget to register the class path for google-services. Failure to do so will lead to you getting Default FirebaseApp is not initialized illegal state exception at runtime.

(b). Register Google Maven's Repository

This you do in your project level build.gradle as well under the allProjects repositories:

        maven {
            url "https://maven.google.com" // Google's Maven repository
        }

So our project level build.gradle should like this:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        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 {
        google()
        jcenter()
        maven {
            url "https://maven.google.com" // Google's Maven repository
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Build.Gradle(App Level)

(a). Add Firebase Dependencies

Go over to app level build.gradle and add Firebase dependencies.

In this case we require two:

  1. Firebase Core - The core classes for Firebase.
  2. FirebaseDatabase - To allow us perform CRUD Firebase Operations.
    implementation 'com.google.firebase:firebase-core:11.8.0'
    implementation 'com.google.firebase:firebase-database:11.8.0'

(b).Apply Google Services Plugin

In the same app level build.gradle, apply the google services plugin just below the dependencies:

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

Please don't forget to apply this plugin. Failure to do so will lead to you getting Default FirebaseApp is not initialized illegal state exception at runtime.

So it the app level build.gradle should look like this:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "info.camposha.firebaselistviewscroller"
        minSdkVersion 14
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    implementation 'com.android.support:appcompat-v7:26.+'
    implementation 'com.android.support:design:26.+'
    implementation 'com.android.support:cardview-v7:26.+'
    implementation 'com.google.firebase:firebase-core:11.8.0'
    implementation 'com.google.firebase:firebase-database:11.8.0'
    testImplementation 'junit:junit:4.12'

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

RESOURCES

colors.xml

We are creating and using custom material theme or style to use with our application. Go check the demo or video to see it.

So we need some custom material colors.

Copy paste this code into colors.xml located in the res/values folder.

You can of course change the colors.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--<color name="colorPrimary">#3F51B5</color>-->
    <!--<color name="colorPrimaryDark">#303F9F</color>-->
    <color name="colorAccent">#FF4081</color>

    <color name="colorPrimary">#f39c12</color>
    <!--<color name="colorPrimaryDark">#125688</color>-->

    <color name="colorPrimaryDark">#FFC107</color>
    <color name="textColorPrimary">#FFFFFF</color>
    <color name="windowBackground">#FFFFFF</color>
    <color name="navigationBarColor">#000000</color>
    <!--<color name="colorAccent">#c8e8ff</color>-->
</resources>

styles.xml

We come here and create a custom material design theme that we will be using.

Here's the code. You can just copy paste this.

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>

    </style>

    <style name="MyMaterialTheme" parent="MyMaterialTheme.Base">

    </style>

    <style name="MyMaterialTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="windowNoTitle">false</item>
        <item name="windowActionBar">true</item>
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

styles.xml(v21)

Also create a secondary style in the res/values folder. Name it styles-v21.

This will be used for Android Lollipop.

<resources>

    <style name="MyMaterialTheme" parent="MyMaterialTheme.Base">
        <item name="android:windowContentTransitions">true</item>
        <item name="android:windowAllowEnterTransitionOverlap">true</item>
        <item name="android:windowAllowReturnTransitionOverlap">true</item>
        <item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
        <item name="android:windowSharedElementExitTransition">@android:transition/move</item>
    </style>
</resources>

AndroidManifest.xml

Those styles we created have to be supplied in our application element's theme attribute in our AndroidManifest.xml for them to be applied.

<application
    ...
    android:theme="@style/MyMaterialTheme"
    ...>

Also we need to add permission for internet connectivity.

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

Here's the full code. If you copy paste this then change the package name to your package name.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.camposha.firebaselistviewscroller">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/MyMaterialTheme">
        <!--android:theme="@style/AppTheme">-->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

In android user interfaces can be created either programmatically or via XML layouts.

The latter is the most common as its the most flexible yet the easiest way.

XML stands for eXtensible Markup Language. We can use it not only to create user interfaces, but also to exchange data.

We start by specifying our root element, in this case a RelativeLayout, which normally arranges items relative to each other.

Inside it at the top we have a TextView, which is a view mostly used to render static text. Think of something like a label. In this case we use it to show the header of our application.

Then we also add a ListView element. This ListView wiil display our data vertically, in a one dimensional manner. The ListView has to be assigned a unique id since it will be referenced from the java code.

We will place it below the header TextView by using the android:layout_below.

We will always be showing a fastScroll. Using this users can scroll through the data. It will be always visible, whether our data exceeds the viewport or not. This is optional.

    android:fastScrollAlwaysVisible="true"

Moreover the ListView will be aligned to the left and start of the parent.

Then to the bottom right of our parent, which in this case is the RelativeLayout, we will display a FloatingActionButton.

When this FAB button is clicked, an input form will be displayed for users to enter data to be sent to Firebase Realtime Database.

We set it's layout gravity to bottom|end to place it at the bottom of the screen.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="info.camposha.firebaselistviewscroller.MainActivity">

    <TextView
        android:id="@+id/headerTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Zen Quotes App"
    android:textStyle="bold"
        android:textAlignment="center"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textColor="@color/colorAccent" />

    <ListView
        android:id="@+id/myListView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:fastScrollAlwaysVisible="true"
        android:layout_below="@+id/headerTextView"
        android:layout_marginTop="30dp" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_gravity="bottom|end"
        android:src="@android:drawable/ic_dialog_email" />

</RelativeLayout>

model.xml

This is a layout that will define how each row in our listview will appear.

That's why we call it model. Our ListView is custom and will be rendering CardViews with multiple Texts. The data will be coming from Firebase Realtime database.

So we place a CardView as a root element of our layout. I have set my cardCornerRadius to 5p, my cardElevation to 10dp and the height to 200dp.

I will use a LinearLayout to arrange items in a vertical manner. These items are basically TextViews.

They will display details of each Teacher object, like name,quote and description.

Here's the full source code:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_margin="10dp"
    card_view:cardCornerRadius="5dp"
    card_view:cardElevation="5dp"
    android:layout_height="200dp">

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

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Name"
                android:id="@+id/nameTextView"
                android:padding="10dp"
                android:textColor="@color/colorAccent"
                android:textStyle="bold"
                android:layout_alignParentLeft="true" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Quote....................."
                android:lines="3"
                android:id="@+id/quoteTextView"
                android:padding="10dp"
                android:layout_alignParentLeft="true" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Description........."
                android:textStyle="italic"
                android:id="@+id/descriptionTextView" />

    </LinearLayout>

</android.support.v7.widget.CardView>

input_dialog.xml

So I now create another layout file called input_dialog.xml.

This layout will define for us a Form, an input form we will use to save data to Firebase. User will type data and click save and then we send or write that data to Firebase.

Immediately data will be retrieved and our ListView will get populated. All this happens in realtime since we are using the powerful Firebase Realtime database.

In my Form I will TextViews, EditTexts ad a save button.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize"
        android:orientation="vertical"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:paddingTop="30dp">

        <TextView
            android:id="@+id/headerTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Enter Teacher Details"
            android:textAlignment="center"
            android:textAppearance="@style/TextAppearance.AppCompat.Large"
            android:textColor="@color/colorAccent" />

            <EditText
                android:id="@+id/nameEditText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:hint= "Teacher's Name" />
            <EditText
                android:id="@+id/quoteEditText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:hint= "Teacher's Quote" />

            <EditText
                android:id="@+id/descEditText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:hint= "Teacher's Description" />

        <Button android:id="@+id/saveBtn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Save"
            android:background="@color/colorAccent"
            android:layout_marginTop="20dp"
            android:textColor="@android:color/white"/>

    </LinearLayout>

</LinearLayout>

Let's now move to our java code.

Java Code

We have multiple classes but only two files:

  1. Teacher.java - Our model class.
  2. MainActivity.java - To contain three inner clases.

Doing it this way makes it easy for beginners to copy paste the code and edit it without creating a bunch of java files.

Let's start.

Teacher.java

This is our model class. It represents a single Teacher. It is our data object.

This class is very important in the way we are working with Firebase. This is because we are saving type safe classes to Firebase.

Then Firebase will automatically read this class and create a JSONObject based on our class names and properties.

You must supply an empty constructor for the class.

package info.camposha.firebaselistviewscroller;

public class Teacher {
    String name, propellant, description;

    public Teacher() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPropellant() {
        return propellant;
    }
    public void setPropellant(String propellant) {
        this.propellant = propellant;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

MainActivity.java

Let's move line by line for our main activity.

1. Create Class,Package and Add Imports

Android projects are normally written in java and kotlin. In both classes are entities to encapsulate code and classes themselves are hosted in packages.

package info.camposha.firebaselistviewscroller;

In both we add import statemments via the import keyword.

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

A class is then created via the class keyword. And the accessibility modifier can be specified.

public class MainActivity..{...}

Both being Object Oriented Programming languages, they support inheritance.

We are using java in this case. Class inheritance in java is achieved through use of the extends keyword:

public class MainActivity extends AppCompatActivity {..}

That has now turned an ordinary class into a special class, an android component called activity.

3. Create a Firebase Helper class

A class that will be responsible for the following:

  1. Writing/Saving data to Firebase From EditText.
  2. Reading/Retrieving data from Firebase.
  3. Setting Firebase Data to Adapter.
  4. Binding the Adapter to ListView for viewability of the data.

Creating the class is easy, we do it inside the MainActivity as an inner class:

public class MainActivity extends AppCompatActivity {
    public class FirebaseHelper { 
        ...
    }
   ....
}    

We will give this FirebaseHelper class some instance fields including:

  1. DatabaseReference - Our Firebase realtime database reference, pointing us to the database.
  2. Boolean field saved - To hold the state of our save to firebase attempt.
  3. ArrayList teachers - To hold all the data we read or fetch from our firebase realtime database.
  4. ListView mListView- To hold a reference to the ListView which will display our data from the Firebase.
  5. Context c - This will hold a reference that will be required while instantiating our custom adapter.

Add them inside the FirebaseHelper class:

        DatabaseReference db;
        Boolean saved;
        ArrayList<Teacher> teachers = new ArrayList<>();
        ListView mListView;
        Context c;

Construct our FirebaseHelper

We have our constructor, a special method that normally gets called when a class in instantiated.

In our case we will inject several dependencies to our class via the constructor.

These include the:

  • Firebase DatabaseReference object.
  • Context object.
  • ListView object.

We will also invoke a method called retrieve() which will peform our firebase data retrieval.

         public FirebaseHelper(DatabaseReference db, Context context, ListView mListView) {
            this.db = db;
            this.c = context;
            this.mListView = mListView;
            this.retrieve();
        }

How to Save Data to Firebase Database

Saving data to Firebase database is very easy. This saving is sometimes called a write operation. Or insert.

We can easily save a custom object like say User or Teacher into firebase database. Then these will be stored in a JSONObject-like nodes.

To save data to Firebase from our app, first let's create a method called save() that takes in a custom POJO object like Teacher:

        public Boolean save(Teacher teacher) {
            //save
        }

In this case the method is returing a Boolean value representing our success state.

Then we first check whether we have valid data. If null is passed to us we just ignore the process and return from execution without saving:

            if (teacher == null) {
                saved = false;
            }

Otherwise we create a try-catch block to catch any runtime exceptions without crashing our app:

            try {
                   //save here
                   saved=true;
                } catch (DatabaseException e) {
                    e.printStackTrace();
                    saved = false;
                }

In Firebase multiple users can save data at the same time by pushing the data via the push() method.

...push()...

But first you need the Child node(like a Table) where we push that data. We can get it from our DatabaseReference object

db.child("Teacher")...

We set the value we want to push or save to Firebase database via the setValue() method.

So this simple statement will push for us our data.

                    db.child("Teacher").push().setValue(teacher);

How to Read/Retrieve Data From Firebase

There are two main ways of reading data from Firebase database:

  1. Using ChildEventListener - Read and Return data node by node.
  2. Uisng ValueEventListener - Read all data at once and return it.

The most commonly used way is the ChildEventListener method. However, our app in this case massively suits us to do it the second way, using the ValueEventListener. Why?

Well remember we want to write and read data to firebase then scroll over to the last added item. So we need all the data so that we can only scroll at the end, to the last added item.

So first we create a method called retrieve(). This method will retrieve data and bind to our ListView.

It can also return that data if you want to use it somewhere.

        public ArrayList<Teacher> retrieve() {..}

First we need to reference the child node in our database, where our data is stored like a Table:

db.child("Teacher")

Then add ValueEventListener to it:

db.child("Teacher").addValueEventListener(...)

Then pas it an annonymous class that contains our callbacks. These callbacks will handle our events.

The callbacks are:

  1. onDataChange() - When our data changes, we can read or retrieve the whole list of data at once.
  2. onCancelled() - When an error is encountered the read operation is cancelled.
db.child("Teacher").addValueEventListener(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    //read data
                    }
                }
                @Override
                public void onCancelled(DatabaseError databaseError) {
                    //handle data
                }
            });

To read our data first we will clear the arraylist that will be containing that data:

                    teachers.clear();

This avoids duplication.

Then we will check if our data snapshot actually exists and that it has some children. We don't want to be reading empty space.

  if (dataSnapshot.exists() && dataSnapshot.getChildrenCount() > 0) {...}

Then get the children and actually loop through them:

 for (DataSnapshot ds : dataSnapshot.getChildren()) {..}

Inside the loop, for each iteration we will actually read the individual data items via the getValue() method of our DataSnapshot object. Then we pass it the generic class so that it converts it to our POJO:

Teacher teacher = ds.getValue(Teacher.class);

But we also have to populate the arraylist so that we have the data we will fill our ListView with:

                            teachers.add(teacher);

Then outside the loop we will instantiate our adapter. ListView is an adapterview and needs an adapter, especially given we are working with complex data(POJOs).

                        adapter = new CustomAdapter(c, teachers);

Then set the adapter to listview via the setAdapter().

                        mListView.setAdapter(adapter);

How to Smooth Scroll ListView to Recently Added Firebase Item

To scroll to the last added item in our ListView, we will make our ListView only scroll to the specified adapter position only when the user interface thread is ready.

Othwerwise we risk the scroll not happening as we expect since the user interface is going to be doing alot at this time.

So we use a Handler, a class that residing in the android.os package which can allow us not only to schedule tasks but also enqueue them.

We are interested in this case in the latter, enqueueing. The Handler does this just within the same thread.

So our scroll task will be enqueued and performed only when the UI thread is ready.

                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                mListView.smoothScrollToPosition(teachers.size());
                            }
                        });

How to Use BaseAdapter with Firebase Data

Now that we've seen how to fetch firebase data, we need to see how to bind them to a custom listview.

For that we need an adapter. In android there are several adapter implementations.

The most popular and commonly used with Adapterviews like ListView and GridView is the BaseAdapter.

Alot of the other adapters like ArrayAdapter actually derive from this baseadapter.

To use it we extend it:

    class CustomAdapter extends BaseAdapter {..}

However, since it's abstract we'll need to implement it's method:

        @Override
        public int getCount() {
            return teachers.size();
        }

        @Override
        public Object getItem(int position) {
            return teachers.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ....
            return convertView
        }

We perform layout inflation and data binding inside the getView() method.

We also listen to our custom listview click events and show the clicked item in a Toast message.

Check the full source code below.

How to Create an Input Form in a Custom Dialog

To actually save data to firebase, that data needs to be typed. Well we need a form where the user types the data.

One way would be to open a new activity to render the form. Another way would be fragment.

Or an even easier way would be to just create a custom dialog that has that form.The dialog can then be invoked or shown when the user clicks the Floating Action Button.

Creating a dialog is very east, you just instantiate the android.app.Dialog class:

        Dialog d = new Dialog(this);

Then set the title:

        d.setTitle("Save To Firebase");

Then what makes it a custom dialog is the fact that we are inflating a custom layout and using it as the content view of the dialog:

        d.setContentView(R.layout.input_dialog);

Now we can come and listen to save button click events:

        saveBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //validate
                //save
                //refresh listview
            }

After obtaining the values of the EditTexts, we can pass them to our POJO class, then perform basic validation:

                if (name != null && name.length() > 0) {
                    //now save to firebase
                }

To actually save:

                    if (helper.save(s)) {
                        //successfully saved
                        //now refresh listview
                    }

Then we refresh the ListView and move to the last saved item.

                        ArrayList<Teacher> fetchedData = helper.retrieve();
                        adapter = new CustomAdapter(MainActivity.this, fetchedData);
                        mListView.setAdapter(adapter);
                        mListView.smoothScrollToPosition(fetchedData.size());

We agreed, remember that we move to a given adapter position in the ListView by invoking the smoothScrollToPosition() method,passing in the position.

Hey, here's the full source code of MainActivity.java:

Full Code

package info.camposha.firebaselistviewscroller;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

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

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    /**********************************FIREBASE HELPER START************************/
    public class FirebaseHelper {

        DatabaseReference db;
        Boolean saved;
        ArrayList<Teacher> teachers = new ArrayList<>();
        ListView mListView;
        Context c;

        /*
       let's receive a reference to our FirebaseDatabase
       */
        public FirebaseHelper(DatabaseReference db, Context context, ListView mListView) {
            this.db = db;
            this.c = context;
            this.mListView = mListView;
            this.retrieve();
        }

        /*
        let's now write how to save a single Teacher to FirebaseDatabase
         */
        public Boolean save(Teacher teacher) {
            //check if they have passed us a valid teacher. If so then return false.
            if (teacher == null) {
                saved = false;
            } else {
                //otherwise try to push data to firebase database.
                try {
                    //push data to FirebaseDatabase. Table or Child called Teacher will be created.
                    db.child("Teacher").push().setValue(teacher);
                    saved = true;

                } catch (DatabaseException e) {
                    e.printStackTrace();
                    saved = false;
                }
            }
            //tell them of status of save.
            return saved;
        }

        /*
        Retrieve and Return them clean data in an arraylist so that they just bind it to ListView.
         */
        public ArrayList<Teacher> retrieve() {
            db.child("Teacher").addValueEventListener(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    teachers.clear();
                    if (dataSnapshot.exists() && dataSnapshot.getChildrenCount() > 0) {
                        for (DataSnapshot ds : dataSnapshot.getChildren()) {
                            //Now get Teacher Objects and populate our arraylist.
                            Teacher teacher = ds.getValue(Teacher.class);
                            teachers.add(teacher);
                        }
                        adapter = new CustomAdapter(c, teachers);
                        mListView.setAdapter(adapter);

                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                mListView.smoothScrollToPosition(teachers.size());
                            }
                        });
                    }
                }

                @Override
                public void onCancelled(DatabaseError databaseError) {
                    Log.d("mTAG", databaseError.getMessage());
                    Toast.makeText(c, "ERROR " + databaseError.getMessage(), Toast.LENGTH_LONG).show();

                }
            });

            return teachers;
        }

    }

    /**********************************CUSTOM ADAPTER START************************/
    class CustomAdapter extends BaseAdapter {
        Context c;
        ArrayList<Teacher> teachers;

        public CustomAdapter(Context c, ArrayList<Teacher> teachers) {
            this.c = c;
            this.teachers = teachers;
        }

        @Override
        public int getCount() {
            return teachers.size();
        }

        @Override
        public Object getItem(int position) {
            return teachers.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = LayoutInflater.from(c).inflate(R.layout.model, parent, false);
            }

            TextView nameTextView = convertView.findViewById(R.id.nameTextView);
            TextView quoteTextView = convertView.findViewById(R.id.quoteTextView);
            TextView descriptionTextView = convertView.findViewById(R.id.descriptionTextView);

            final Teacher s = (Teacher) this.getItem(position);

            nameTextView.setText(s.getName());
            quoteTextView.setText(s.getPropellant());
            descriptionTextView.setText(s.getDescription());

            //ONITECLICK
            convertView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(c, s.getName(), Toast.LENGTH_SHORT).show();
                }
            });
            return convertView;
        }
    }

    /**********************************MAIN ACTIVITY CONTINUATION************************/
    //instance fields
    DatabaseReference db;
    FirebaseHelper helper;
    CustomAdapter adapter;
    ListView mListView;
    EditText nameEditTxt, quoteEditText, descriptionEditText;

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

        mListView = (ListView) findViewById(R.id.myListView);
        //initialize firebase database
        db = FirebaseDatabase.getInstance().getReference();
        helper = new FirebaseHelper(db, this, mListView);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mListView.smoothScrollToPosition(4);
                displayInputDialog();
            }
        });
    }

    //DISPLAY INPUT DIALOG
    private void displayInputDialog() {
        //create input dialog
        Dialog d = new Dialog(this);
        d.setTitle("Save To Firebase");
        d.setContentView(R.layout.input_dialog);

        //find widgets
        nameEditTxt = d.findViewById(R.id.nameEditText);
        quoteEditText = d.findViewById(R.id.quoteEditText);
        descriptionEditText = d.findViewById(R.id.descEditText);
        Button saveBtn = d.findViewById(R.id.saveBtn);

        //save button clicked
        saveBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //get data from edittexts
                String name = nameEditTxt.getText().toString();
                String quote = quoteEditText.getText().toString();
                String description = descriptionEditText.getText().toString();

                //set data to POJO
                Teacher s = new Teacher();
                s.setName(name);
                s.setPropellant(quote);
                s.setDescription(description);

                //perform simple validation
                if (name != null && name.length() > 0) {
                    //save data to firebase
                    if (helper.save(s)) {
                        //clear edittexts
                        nameEditTxt.setText("");
                        quoteEditText.setText("");
                        descriptionEditText.setText("");

                        //refresh listview
                        ArrayList<Teacher> fetchedData = helper.retrieve();
                        adapter = new CustomAdapter(MainActivity.this, fetchedData);
                        mListView.setAdapter(adapter);
                        mListView.smoothScrollToPosition(fetchedData.size());
                    }
                } else {
                    Toast.makeText(MainActivity.this, "Name Must Not Be Empty Please", Toast.LENGTH_SHORT).show();
                }
            }
        });

        d.show();
    }

}

Result

Here's our result, Firebase ListView with CardViews - Write/Save data and Read/Retrieve data and scroll to last saved item.

Android Firebase ListView - Save,Read, Scroll To Last saved Item

Issues to Keep in Mind

  1. If you are getting a java.lang.illegalStateException after following the above steps then it means you have missed a step. This error gets raised if you are trying to access the FirebaseDatabase without initialization.

The most common error is missing to apply the google services plugin or forgetting to register its class path in our app level and project level buil.gradle files respectively.

  1. Your POJO class must be a standalone class and not an inner class. Otherwise you'll get errors at runtime that your POJO/Data Object class does not define an empty constructor, even uf it does.

  2. You must add internet permission in your AndroidManifest.xml or else you cannot make Firebase API calls. Some emulators do not access internet by default, so open up a browser and ensure you can access internet first.

Best Regards.

Comments