Saturday, 15 December 2012

Custom Loader : Loading data using Loader Manager in android asynchronously

Loader is useful while maintaining life cycle of activity and avoiding performing big task again and again. Loader provide a way to keep old data while changing orientation. Every loader has its own unique ID and its created only once. Next time it will use the previous instance.

Loader is discussed at various place but creating your own custom loader is the topic remains to discuss. Let take one case where loader is useful in android

  Suppose application download data from network and show inside a list. suddenly user change phone orientation. Data will start downloading again. To avoid this case, Loader are right thing to use

Loader life cycle include three method

  • public Loader<DataHandler> onCreateLoader(int arg0, Bundle arg1) , called when loader initialize once only
  • public void onLoadFinished(Loader<DataHandler> arg0, DataHandler arg1) every time when applciation interact with Loader. And return data

Note: Here DataHandler is application custom class

  • public void onLoaderReset(Loader<DataHandler> arg0)  to change UI and remove previous view

Now this article will include steps and description to create its own custom loader . It had one list fragment. And after download data using AsyncTaskLoader<DataHandler>, we shows data inside List Fragment

1) One fragment activity is base activity that include on list fragment inside main frame layout
2) Now create List Fragment and initialize the Loader with Loader manager like         

 getActivity().getSupportLoaderManager().initLoader(10, null, this). 

Loader manager has three argument Loader _ID, Bundle data and LoaderManager.LoaderCallbacks<DataHandler>. Now it will force you to override required method of       Loader Manager callback

First will be create loader, here we start one Asynchronous Loader to load data. 


  @Override
public Loader<DataHandler> onCreateLoader(int arg0, Bundle arg1) {
AsynChro asynChro = new AsynChro(ListFragmentExp.this.getActivity());
asynChro.forceLoad();
return asynChro;
}

Loader reset call everytime it interact with loader

@Override
public void onLoaderReset(Loader<DataHandler> arg0) {
mlisListView.setAdapter(null);
}

Loader loaded then it call this method , return with data everytime once it downloaded


       @Override
public void onLoadFinished(Loader<DataHandler> arg0, DataHandler arg1) {
mlisListView.setAdapter(new DataBinder(getActivity(), arg1.getData()));
setListShown(true);
}

3) Once we downloaded data, we just have traditional way to set it inside List Fragment using BaseAdapter

                                   

  

These above images show how loader maintain life cycle of data when you change your orientation . LoaderManager can return any kind of data. Here i have my custom class DataHolder and i am returning data as DataHolder

So Lets go step by step with loader in coding

Activity Class for initiation


package com.loader;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.widget.FrameLayout;

public class MainActivity extends FragmentActivity{
    FragmentTransaction mTFragmentTransaction;
    FragmentManager mManFragmentManager;
    ListFragmentExp mListFragment;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListFragment = new ListFragmentExp();
        FrameLayout cursor = (FrameLayout) findViewById(R.id.listFragment);
        mManFragmentManager = getSupportFragmentManager();

        if (mManFragmentManager.findFragmentByTag("Tag") == null) {
            mTFragmentTransaction = mManFragmentManager.beginTransaction();
            mTFragmentTransaction.add(cursor.getId(), mListFragment, "Tag");
            mTFragmentTransaction.commit();
        }
    }
}


A List Fragment loading data using LoaderManager and displaying


package com.loader;

import java.util.ArrayList;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;

public class ListFragmentExp extends ListFragment implements
        LoaderManager.LoaderCallbacks<DataHandler> {

    // This is the Adapter being used to display the list's data.
    BaseAdapter mAdapter;
    public static ListView mlisListView;
    // If non-null, this is the current filter the user has provided.
    String mCurFilter;

    public static FragmentActivity mAcFragmentActivity;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mAcFragmentActivity = getActivity();
        // Give some text to display if there is no data. In a real
        // application this would come from a resource.
        setEmptyText("No phone numbers");

        // Create an empty adapter we will use to display the loaded data.
        setListAdapter(mAdapter);

        // Start out with a progress indicator.
        setListShown(false);
        mlisListView = getListView();
        getActivity().getSupportLoaderManager().initLoader(10, null, this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public Loader<DataHandler> onCreateLoader(int arg0, Bundle arg1) {
        AsynChro asynChro = new AsynChro(ListFragmentExp.this.getActivity());
        asynChro.forceLoad();
        return asynChro;
    }

    @Override
    public void onLoadFinished(Loader<DataHandler> arg0, DataHandler arg1) {
        mlisListView.setAdapter(new DataBinder(getActivity(), arg1.getData()));
        setListShown(true);
    }

    @Override
    public void onLoaderReset(Loader<DataHandler> arg0) {
        mlisListView.setAdapter(null);
    }

    public static class AsynChro extends AsyncTaskLoader<DataHandler> {

        DataHandler mHaDataHandler;

        public AsynChro(Context context) {
            super(context);
            mHaDataHandler = new DataHandler();
        }

        @Override
        public DataHandler loadInBackground() {
            ArrayList<String> temp = new ArrayList<String>();
            for (int i = 0; i < 100; i++) {
                temp.add("Counting-----" + i);
            }
            try {
                synchronized (this) {
                    wait(5000);
                }
            } catch (Exception e) {
                e.getMessage();
            }
            mHaDataHandler.setData(temp);
            return mHaDataHandler;
        }

        @Override
        public void deliverResult(DataHandler data) {
            super.deliverResult(data);
            mlisListView.setAdapter(new DataBinder(mAcFragmentActivity, data
                    .getData()));
        }
    }

}


And finally one object to save data. I make DataHandler class to save data


package com.loader;

import java.util.ArrayList;

public class DataHandler {

    ArrayList<String> mListStrings = new ArrayList<String>();

    public void setData(ArrayList<String> temp) {
        mListStrings = temp;
    }

    public ArrayList<String> getData() {
        return mListStrings;
    }
}
Now Binding all data inside a List Fragment using BaseAdapter


package com.loader;

import java.util.ArrayList;

import android.graphics.Color;
import android.support.v4.app.FragmentActivity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class DataBinder extends BaseAdapter {
    ArrayList<String> mLisArrayList;

    FragmentActivity mAtcFragmentActivity;

    public DataBinder(FragmentActivity mActivity, ArrayList<String> mList) {
        mLisArrayList = mList;
        mAtcFragmentActivity = mActivity;
    }

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

    @Override
    public Object getItem(int position) {
        return null;
    }

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

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

        TextView mTextView;
        if (convertView == null) {
            mTextView = new TextView(mAtcFragmentActivity);

            mTextView.setLayoutParams(new ListView.LayoutParams(
                    LayoutParams.FILL_PARENT, 60));
            mTextView.setGravity(Gravity.CENTER_HORIZONTAL
                    | Gravity.CENTER_VERTICAL);
            mTextView.setTextSize(20);
            mTextView.setBackgroundColor(Color.GRAY);
            mTextView.setTextColor(Color.GREEN);
            convertView = mTextView;
        } else {
            mTextView = (TextView) convertView;
        }
        mTextView.setText(mLisArrayList.get(position));
        return convertView;
    }
}

Feel free to download code

Download Source Code