June 1, 2016

Custom Loaders with SQLite in Android

Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:

  • They are available to every Activity and Fragment.
  • They provide asynchronous loading of data.
  • They monitor the source of their data and deliver new results when the content changes.
  • They automatically reconnect to the last loader’s cursor when being recreated after a configuration change. Thus, they don’t need to re-query their data.

You can read more aboutLoaders from here.

Read Loaders with SimpleCursorAdapter from here.

We will create a Sqlite Database with a Table and three columns, id, name and company.

We will try to query data with Loaders and SimpleCursorAdapter.

The Loaders will load the data from the database to the ListView.

Where there is new Data added, the Loader will automatically reload the data in the ListView.

Lets see how to do this.

I am using a class called DemoItem which contains three members – name and company.
Our Sqlite database has the same structure with an autoincrement ID.

package com.coderzheaven.myapplication;public class DemoItem {String name, company;public String getName() {return name;}public void setName(String name) { = name;}public String getCompany() {return company;}public void setCompany(String company) { = company;}public void set(String name, String company) { = name; = company;}}

Here is our Database Helper class

package com.coderzheaven.myapplication;import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;import android.util.Log;import java.util.LinkedList;import java.util.List;public class MySQLiteHelper extends SQLiteOpenHelper {// Database Versionprivate static final int DATABASE_VERSION = 1;// Database Nameprivate static final String DATABASE_NAME = "DemoDB";private static final String TABLE_NAME = "Demo";public static final String KEY_ID = "_id";public static final String KEY_NAME = "NAME";public static final String KEY_COMPANY = "COMPANY";private static final String[] COLUMNS = {KEY_ID, KEY_NAME, KEY_COMPANY};public MySQLiteHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("+ KEY_ID + " INTEGER primary key AUTOINCREMENT,"+ KEY_NAME + " TEXT,"+ KEY_COMPANY + " TEXT);";db.execSQL(CREATE_TABLE);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);this.onCreate(db);}public void addItem(DemoItem item) {// Get reference to writable DBSQLiteDatabase db = this.getWritableDatabase();// Create ContentValues to add key "column"/valueContentValues values = new ContentValues();values.put(KEY_NAME, item.getName()); // get titlevalues.put(KEY_COMPANY, item.getCompany()); // get author// Insertdb.insert(TABLE_NAME, // tablenull, //nullColumnHackvalues); // key/value -> keys = column names/ values = column values// close the dbdb.close();}public List<DemoItem> getAllItems() {List<DemoItem> items = new LinkedList<DemoItem>();String query = "SELECT* FROM " + TABLE_NAME;SQLiteDatabase db = this.getWritableDatabase();Cursor cursor = db.rawQuery(query, null);DemoItem item = null;if (cursor.moveToFirst()) {do {item = new DemoItem();item.setId(cursor.getString(0));item.setName(cursor.getString(1));item.setCompany(cursor.getString(2));items.add(item);} while (cursor.moveToNext());}return items;}}

Below is my Adapter class for the ListView.

package com.coderzheaven.myapplication;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.TextView;import java.util.List;public class CustomAdapter extends BaseAdapter{private Context context;private List<demoitem> data;private static LayoutInflater inflater = null;public CustomAdapter(Context context, List</demoitem><demoitem> data) {this.context = context; = data;inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);}public int getCount() {return data.size();}public Object getItem(int position) {return position;}public long getItemId(int position) {return position;}public static class ViewHolder {public TextView id;public TextView name;public TextView company;}public View getView(int position, View convertView, ViewGroup parent) {View vi = convertView;ViewHolder holder;if (convertView == null) {vi = inflater.inflate(R.layout.list_row, null);holder = new ViewHolder(); = (TextView) vi.findViewById(; = (TextView) vi.findViewById(; = (TextView) vi.findViewById(;vi.setTag(holder);} else {holder = (ViewHolder) vi.getTag();}DemoItem demoItem = data.get(position);"ID = " + demoItem.getId());;;return vi;}}<font color="orange">The Custom Loader Class.</font>package com.coderzheaven.myapplication;import android.content.AsyncTaskLoader;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.util.Log;import java.util.List;public class SampleLoader extends AsyncTaskLoader<List<DemoItem>> {public static final String TAG = "SampleLoader";// We hold a reference to the Loader’s data here.private List<DemoItem> mData;Context ctx;public SampleLoader(Context ctx) {super(ctx);this.ctx = ctx;ctx.registerReceiver(mObserver, new IntentFilter("my-event"));}@Overridepublic List<DemoItem> loadInBackground() {// This method is called on a background thread and should generate a// new set of data to be delivered back to the client.MySQLiteHelper mySQLiteHelper = new MySQLiteHelper(ctx);List<DemoItem> allItems = mySQLiteHelper.getAllItems();Log.i(TAG,"Size : " + allItems.size());return allItems;}@Overridepublic void deliverResult(List<DemoItem> data) {if (isReset()) {// The Loader has been reset; ignore the result and invalidate the data.releaseResources(data);return;}// Hold a reference to the old data so it doesn't get garbage collected.// We must protect it until the new data has been delivered.List<DemoItem> oldData = mData;mData = data;if (isStarted()) {// If the Loader is in a started state, deliver the results to the// client. The superclass method does this for us.super.deliverResult(data);}// Invalidate the old data as we don't need it any more.if (oldData != null && oldData != data) {releaseResources(oldData);}}@Overrideprotected void onStartLoading() {if (mData != null) {// Deliver any previously loaded data immediately.deliverResult(mData);}if (takeContentChanged() || mData == null) {// When the observer detects a change, it should call onContentChanged()// on the Loader, which will cause the next call to takeContentChanged()// to return true. If this is ever the case (or if the current data is// null), we force a new load.forceLoad();Intent intent = new Intent("my-event");intent.putExtra("message", "message");getContext().sendBroadcast(intent);}}@Overrideprotected void onStopLoading() {// The Loader is in a stopped state, so we should attempt to cancel the// current load (if there is one).cancelLoad();// Note that we leave the observer as is. Loaders in a stopped state// should still monitor the data source for changes so that the Loader// will know to force a new load if it is ever started again.}@Overrideprotected void onReset() {// Ensure the loader has been stopped.onStopLoading();// At this point we can release the resources associated with 'mData'.if (mData != null) {releaseResources(mData);mData = null;}// The Loader is being reset, so we should stop monitoring for changes.if (mObserver != null) {try {getContext().unregisterReceiver(mObserver);} catch (Exception e) {e.printStackTrace();}mObserver = null;}}@Overridepublic void onCanceled(List<DemoItem> data) {// Attempt to cancel the current asynchronous load.super.onCanceled(data);// The load has been canceled, so we should release the resources// associated with 'data'.releaseResources(data);}private void releaseResources(List<DemoItem> data) {// For a simple List, there is nothing to do. For something like a Cursor, we// would close it in this method. All resources associated with the Loader// should be released here.}private BroadcastReceiver mObserver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// Extract data included in the Intent//String message = intent.getStringExtra("message");Log.d("receiver", "Got message: ");}};}

Here is the Complete Fragment Class that implements the Custom Loader.

package com.coderzheaven.myapplication;import;import;import android.content.Context;import android.content.Loader;import android.os.Bundle;import;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ListView;import java.util.List;public class MainActivityFragment extends Fragment implements LoaderCallbacks<List<DemoItem>>, View.OnClickListener {private static final String TAG = "MainActivityFragment";private static final int LOADER_ID = 1;Context context;MySQLiteHelper m;SampleLoader sampleLoader;CustomAdapter customAdapter;ListView mList;List<DemoItem> allList;FloatingActionButton fab;public MainActivityFragment() {this.context = getActivity();}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_main, container, false);fab = (FloatingActionButton) view.findViewById(;fab.setOnClickListener(this);m = new MySQLiteHelper(getActivity());getLoaderManager().initLoader(LOADER_ID, null, this);mList = (ListView) view.findViewById(;allList = m.getAllItems();customAdapter = new CustomAdapter(getActivity(), allList);mList.setAdapter(customAdapter);return view;}public void add() {DemoItem d = new DemoItem();d.set("CoderzHeaven", "Company");m.addItem(d);sampleLoader.onContentChanged();}@Overridepublic Loader<List<DemoItem>> onCreateLoader(int id, Bundle args) {sampleLoader = new SampleLoader(getActivity());return sampleLoader;}@Overridepublic void onLoadFinished(Loader<List<DemoItem>> loader,List<DemoItem> cursor) {Log.i(TAG, "onLoadFinished " + cursor.size());allList.clear();allList.addAll(m.getAllItems());customAdapter.notifyDataSetChanged();mList.setSelection(allList.size() - 1);}@Overridepublic void onLoaderReset(Loader<List<DemoItem>> loader) {Log.i(TAG, "onLoaderReset");}@Overridepublic void onClick(View v) {if (v == fab)add();}}

You can download the complete source code from here.

Website PinFacebook TwitterMyspaceFriendfeedTechnoratidel.icio.usDiggGoogleStumbleUponPremium Responsive

Leave a Reply Cancel reply