Loading holder view in Android

    Most of mobile applications use online data through Internet connection. This mean users always need a period time to waiting data loaded from server to display on our applications interface. The matter here is during this loading process, as an fronted-end developer, what should we do with app interface without data? The simplest way is show a progress dialog, but in some cases, it's disadvantage because it restricts user actions and cause uncomfortable feeling.
    Some popular apps has an another approach: using loader view/loader holder. It's the default interface, visible when app launched and the data is blank. When data was loaded to device and available, loader holder view will disappear and real-data was displayed (usually by TextView and ImageView). This approach make your app seem smoothly and more friendly!
    In this post, I will present a third-party called loaderviewlibrary to make this interface, output of loader view may be like this:

Importing library

    Adding this dependency to your app-level build.gradle to use this library:
dependencies {
    compile 'com.elyeproj.libraries:loaderviewlibrary:1.0.3'
}
    Now, we have 2 objects to build the loader view named LoaderTextView and LoaderImageView.

Usages in XML

    Two above classes is subclass of TextView and ImageView so we can use them normally in xml files.
    Define Loader View for TextView in layout XML:
<com.elyeproj.loaderviewlibrary.LoaderTextView
     android:layout_width="match_parent"
     android:layout_height="wrap_content" />
    Loader View for ImageView defined in layout XML:
<com.elyeproj.loaderviewlibrary.LoaderImageView
     android:layout_width="100dp"
     android:layout_height="100dp" />
    Requirement: your project min-sdk must be 16 or higher.

Sample project

    Now, I will provide a sample project about using this library. I will load JSON and Bitmap from URLs and display to a list view. Declaring the main activity layout containing only a ListView first:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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"
    tools:context="info.devexchanges.loaderview.MainActivity">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="none" />
</RelativeLayout>
    In each list view row (item), I have a LoaderTextView and LoaderImageView to display data later:
item_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:paddingBottom="@dimen/activity_horizontal_margin">

    <com.elyeproj.loaderviewlibrary.LoaderImageView
        android:id="@+id/image_view"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerVertical="true" />

    <com.elyeproj.loaderviewlibrary.LoaderTextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_toRightOf="@id/image_view"
        android:gravity="left"
        android:maxHeight="120dp" />
</RelativeLayout>
    Customizing a ListView adapter based on ArrayAdapter:
ListViewAdapter.java
package info.devexchanges.loaderview;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

import java.util.ArrayList;

public class ListViewAdapter extends ArrayAdapter<String> {

    private Activity activity;
    private boolean isLoadImage;
    private final static String IMAGE_URL = "http://i.imgur.com/cReBvDB.png";

    public ListViewAdapter(Activity context, int resource, ArrayList<String> objects, boolean isLoadImage) {
        super(context, resource, objects);
        this.activity = context;
        this.isLoadImage = isLoadImage;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        // If holder not exist then locate all view from UI file.
        if (convertView == null) {
            // inflate UI from XML file
            convertView = inflater.inflate(R.layout.item_listview, parent, false);
            // get all UI view
            holder = new ViewHolder(convertView);
            // set tag for holder
            convertView.setTag(holder);
        }  else {
            // if holder created, get tag from view
            holder = (ViewHolder) convertView.getTag();
        }

        if (!getItem(position).equals("")) {
            holder.countryName.setText(getItem(position));
        }
        if (isLoadImage) {
            Picasso.with(activity).load(IMAGE_URL).into(holder.imageView);
        }

        return convertView;
    }

    private class ViewHolder{

        private ImageView imageView;
        private TextView countryName;

        public ViewHolder (View view) {
            imageView = (ImageView)view.findViewById(R.id.image_view);
            countryName = (TextView)view.findViewById(R.id.text_view);
        }
    }
}
    In the main activity, data will be loaded from URL when click on a button in option menu. Before it's clicked, list view data is blank and the loader holders will appear. Source code for this activity:
MainActivity.java
package info.devexchanges.loaderview;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private ArrayList<String> strings;
    private ArrayAdapter<String> adapter;

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

        listView = (ListView)findViewById(R.id.list_view);
        strings = new ArrayList<>();

        for (int i = 0; i < 5; i++) {
            strings.add("");
        }

        adapter = new ListViewAdapter(this, R.layout.item_listview, strings, false);
        listView.setAdapter(adapter);
    }

    private void loadJSONDataFromURL() {
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
               new GetJSONTask(MainActivity.this).execute();
            }
        }, 1000);
        Log.i("Main", "load data");
    }

    //parsing json after getting from Internet
    public void parseJsonResponse(String result) {
        strings.clear();
        try {
            JSONObject json = new JSONObject(result);
            JSONArray jArray = new JSONArray(json.getString("message"));
            for (int i = 0; i < jArray.length(); i++) {
                JSONObject jObject = jArray.getJSONObject(i);
                strings.add(jObject.getString("name"));
            }

            adapter.notifyDataSetChanged();
            Log.i("Main", "finish load data");
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.load) {
            adapter = new ListViewAdapter(this, R.layout.item_listview, strings, true);
            listView.setAdapter(adapter);
            loadJSONDataFromURL();
        }
        return super.onOptionsItemSelected(item);
    }
}
    And the menu file:
res/menu/main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/load"
        android:title="Load Data"
        android:icon="@drawable/load"
        app:showAsAction="always" />
</menu>
    Important Note:
-    Request Internet permission in AndroidManifest.xml to get data from URLs:
<uses-permission android:name="android.permission.INTERNET"/>
-    I use Picasso library to load image from an URL, please add it's dependency to app-level build.gradle:
compile 'com.squareup.picasso:picasso:2.5.2'

Running application

    Output of this project:

Conclusions

    I have just present the simplest way to creating a loader view (loading holder) to "wait for data loading" in Android application. If your app has much complicated or big data, you should take attention in this solution instead of using progress dialog, your app UX will be better!
    Moreover, you can go to this library page on Github to read more details about it. Hope this is helpful with your work. Finally, you can get my full code by clicking the button below.

Share


Previous post
« Prev Post
Next post
Next Post »