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 about Loaders 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) { this.name = name; } public String getCompany() { return company; } public void setCompany(String company) { this.company = company; } public void set(String name, String company) { this.name = name; this.company = 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 Version private static final int DATABASE_VERSION = 1; // Database Name private 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); } @Override public 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); } @Override public 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 DB SQLiteDatabase db = this.getWritableDatabase(); // Create ContentValues to add key "column"/value ContentValues values = new ContentValues(); values.put(KEY_NAME, item.getName()); // get title values.put(KEY_COMPANY, item.getCompany()); // get author // Insert db.insert(TABLE_NAME, // table null, //nullColumnHack values); // key/value -> keys = column names/ values = column values // close the db db.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; this.data = 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(); holder.id = (TextView) vi.findViewById(R.id.text0); holder.name = (TextView) vi.findViewById(R.id.text1); holder.company = (TextView) vi.findViewById(R.id.text2); vi.setTag(holder); } else { holder = (ViewHolder) vi.getTag(); } DemoItem demoItem = data.get(position); holder.id.setText("ID = " + demoItem.getId()); holder.name.setText(demoItem.getName()); holder.company.setText(demoItem.getCompany()); return vi; } } <font color="orange">The Custom Loader Class.</font> [java] 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")); } @Override public 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; } @Override public 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); } } @Override protected 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); } } @Override protected 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. } @Override protected 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; } } @Override public 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() { @Override public 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 android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Loader; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; 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(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_main, container, false); fab = (FloatingActionButton) view.findViewById(R.id.fab); fab.setOnClickListener(this); m = new MySQLiteHelper(getActivity()); getLoaderManager().initLoader(LOADER_ID, null, this); mList = (ListView) view.findViewById(R.id.lv); 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(); } @Override public Loader<List<DemoItem>> onCreateLoader(int id, Bundle args) { sampleLoader = new SampleLoader(getActivity()); return sampleLoader; } @Override public 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); } @Override public void onLoaderReset(Loader<List<DemoItem>> loader) { Log.i(TAG, "onLoaderReset"); } @Override public void onClick(View v) { if (v == fab) add(); } }
You can download the complete source code from here.
thanks
awesome and great tutorials 🙂
amazing tutorial this is what i am looking for……please the source code this turoial.the above link is broken
Dear Caviru,
I have updated the link. Sorry for the error.