Android: offline capabilities using Firebase

 

Firebase apps work offline and enabling disk persistence allows your app to keep all of its state even after an app restart. Firebase also provides several tools for monitoring presence and connectivity state, as we will cover in the lines bellow.

First of all, in order to be able to use Firebase on Android we need to:

1. Using Gradle we need to add the following lines:

dependencies {
    compile 'com.firebase:firebase-client-android:2.5.0+'
}
packagingOptions {
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE-FIREBASE.txt'
        exclude 'META-INF/NOTICE'
    }

2. Add internet permission in the manifest file:

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

Bellow is the content of 3 files(MainActivity.java, activity_main.xml and item_list_view_news.xml).
The result of this files is a simple Android screen displaying if your device is connected to internet or not and a list with all the news from the news node in Firebase.
To enable the offline capabilities of Firebase all you have to do is to write these few lines of code:

Firebase.getDefaultConfig().setPersistenceEnabled(true);

Firebase myFirebaseRef = new Firebase(firebaseUrl);

myFirebaseRef.keepSynced(true);

Firebase synchronizes and stores a local copy of the data for active listeners. In addition, you can keep specific locations in sync.
The client will automatically download the data at these locations and keep it in sync even if the reference has no active listeners.
By default, 10MB of previously synced data will be cached. This should be enough for most applications. If the cache outgrows its configured size, Firebase will purge data that has been used least recently. Data that is kept in sync, will not be purged from the cache.

Content of MainActivity.java:

package twolioncubs.androidusingfirebase;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.firebase.client.ChildEventListener;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;
import com.google.android.gms.appindexing.Action;
import com.google.android.gms.appindexing.AppIndex;
import com.google.android.gms.common.api.GoogleApiClient;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    /**
     * ATTENTION: This was auto-generated to implement the App Indexing API.
     * See https://g.co/AppIndexing/AndroidStudio for more information.
     */
    private GoogleApiClient client;
    private List&amp;lt;News&amp;gt; listNews = new ArrayList&amp;lt;News&amp;gt;();
    private ListAdapter listAdapter;

    private String firebaseUrl = &amp;quot;https://chat-with-2lc.firebaseio.com/&amp;quot;;

    private TextView textViewConnected;

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

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        textViewConnected = (TextView) findViewById(R.id.textViewConnected);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, &amp;quot;Replace with your own action&amp;quot;, Snackbar.LENGTH_LONG)
                        .setAction(&amp;quot;Action&amp;quot;, null).show();
            }
        });

        ListView listView = (ListView) findViewById(R.id.listViewNews);
        listAdapter = new ListAdapter(this, R.layout.item_list_view_news, listNews);
        listView.setAdapter(listAdapter);

        Firebase.setAndroidContext(this);
        Firebase.getDefaultConfig().setPersistenceEnabled(true);

        Firebase myFirebaseRef = new Firebase(firebaseUrl);
        myFirebaseRef.keepSynced(true);

        handleInternetConnection();

        handleNews(myFirebaseRef);

        // ATTENTION: This was auto-generated to implement the App Indexing API.
        // See https://g.co/AppIndexing/AndroidStudio for more information.
        client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
    }

    public void handleNews(Firebase myFirebaseRef) {
        myFirebaseRef.child(&amp;quot;news&amp;quot;).addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String previousChildKey) {
                String key = dataSnapshot.getKey();
                Map&amp;lt;String, String&amp;gt; value = (Map&amp;lt;String, String&amp;gt;) dataSnapshot.getValue();

                News news = new News();
                news.setKey(key);
                news.setTitle(value.get(&amp;quot;title&amp;quot;));
                news.setContent(value.get(&amp;quot;content&amp;quot;));
                listNews.add(news);

                listAdapter.notifyDataSetChanged();
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                String key = dataSnapshot.getKey();
                Map&amp;lt;String, String&amp;gt; value = (Map&amp;lt;String, String&amp;gt;) dataSnapshot.getValue();

                for(News n: listNews) {
                    if(n.getKey().equalsIgnoreCase(key)) {
                        n.setTitle(value.get(&amp;quot;title&amp;quot;));
                        n.setContent(value.get(&amp;quot;content&amp;quot;));
                    }
                }

                listAdapter.notifyDataSetChanged();
            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                String key = dataSnapshot.getKey();
                Map&amp;lt;String, String&amp;gt; value = (Map&amp;lt;String, String&amp;gt;) dataSnapshot.getValue();

                for(News n: listNews) {
                    if(n.getKey().equalsIgnoreCase(key)) {
                        listNews.remove(n);
                    }
                }

                listAdapter.notifyDataSetChanged();
            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onCancelled(FirebaseError error) {
                System.out.println(error);  //prints &amp;quot;Do you have data? You'll love Firebase.&amp;quot;
            }
        });
    }

    public void handleInternetConnection() {
        Firebase connectedRef = new Firebase(firebaseUrl + &amp;quot;.info/connected&amp;quot;);
        connectedRef.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot snapshot) {
                boolean connected = snapshot.getValue(Boolean.class);
                if (connected) {
                    System.out.println(&amp;quot;connected&amp;quot;);
                } else {
                    System.out.println(&amp;quot;not connected&amp;quot;);
                }
                textViewConnected.setText(&amp;quot;Connected : &amp;quot; + connected);
            }
            @Override
            public void onCancelled(FirebaseError error) {
                System.err.println(&amp;quot;Listener was cancelled&amp;quot;);
            }
        });
    }

    class News {
        private String key;
        private String title;
        private String content;

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }
    }

    class ListAdapter extends ArrayAdapter&amp;lt;News&amp;gt; {

        public ListAdapter(Context context, int resource, List&amp;lt;News&amp;gt; items) {
            super(context, resource, items);
        }

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

            View v = convertView;

            if (v == null) {
                LayoutInflater vi;
                vi = LayoutInflater.from(getContext());
                v = vi.inflate(R.layout.item_list_view_news, null);
            }

            News p = getItem(position);

            if (p != null) {
                TextView label = (TextView) v.findViewById(R.id.myListItemLabel);
                TextView value = (TextView) v.findViewById(R.id.myListItemValue);

                if (label != null) {
                    label.setText(String.valueOf(p.getTitle()));
                }

                if (value != null) {
                    value.setText(String.valueOf(p.getContent()));
                }

            }

            return v;
        }

    }
}

Content of activity_main.xml:

</pre>
<pre><?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
    android:fitsSystemWindows="true"
    tools:context="twolioncubs.androidusingfirebase.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

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

    <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"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context="twolioncubs.androidusingfirebase.MainActivity"
        tools:showIn="@layout/activity_main">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/textViewConnected"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Connected :"/>

            <ListView
                android:id="@+id/listViewNews"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:layout_gravity="center_horizontal" />
        </LinearLayout>





    </RelativeLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_input_add"
        android:tint="@android:color/white"
        />

</android.support.design.widget.CoordinatorLayout></pre>
<pre>

Content of item_list_view_news.xml:

</pre>
<pre><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:padding="8dp">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:orientation="vertical">

        <TextView
            android:id="@+id/myListItemLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="label"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/myListItemValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="value"
            android:textAppearance="?android:attr/textAppearanceSmall" />
    </LinearLayout>


</RelativeLayout></pre>
<pre>

 

Screenshot_2016-02-08-18-49-10

 

firebase_news

 

 

2 thoughts on “Android: offline capabilities using Firebase

  1. Firebase just introduced FirebaseUI who can manage listviews and recyclerviews to make your life better 😀

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s