Content loaders provide you with a mechanism to load data from a content provider or other data source for display in a UI component, such as Activity or Fragment. These are the benefits that loaders provide:
- Running on a separate thread
- Simplifying thread management by providing callback methods
- Loaders persist and cache results across configuration changes, which prevents duplicated queries
- We can implement and be observers to monitor changes in the data
We will create our content loader implementation. First, we need to update the Adapter class. Since we will deal with cursors, we will use a CursorAdapter instead of BaseAdapter. CursorAdapter accepts a Cursor instance as a parameter in the primary constructor. The CursorAdapter implementation is much simpler than the one we have right now. Open EntryAdapter and update it as follows:
class EntryAdapter(ctx: Context, crsr: Cursor) : CursorAdapter(ctx,
crsr) { override fun newView(p0: Context?, p1: Cursor?, p2: ViewGroup?):
View { val inflater = LayoutInflater.from(p0) return inflater.inflate(R.layout.adapter_entry, null) } override fun bindView(p0: View?, p1: Context?, p2: Cursor?) { p0?.let { val label = p0.findViewById<TextView>(R.id.title) label.text = cursor.getString( cursor.getColumnIndexOrThrow(DbHelper.COLUMN_TITLE) ) } } }
We have the following two methods to override:
- newView(): This returns the instance of the view to populate with data
- bindView(): This populates data from the Cursor instance
Finally, let's update our ItemsFragment class, so it uses the content loader implementation:
class ItemsFragment : BaseFragment() { ... private var adapter: EntryAdapter? = null ... private val loaderCallback = object :
LoaderManager.LoaderCallbacks<Cursor> { override fun onLoadFinished(loader: Loader<Cursor>?, cursor:
Cursor?) { cursor?.let { if (adapter == null) { adapter = EntryAdapter(activity, cursor) items.adapter = adapter } else { adapter?.swapCursor(cursor) } } } override fun onLoaderReset(loader: Loader<Cursor>?) { adapter?.swapCursor(null) } override fun onCreateLoader(id: Int, args: Bundle?):
Loader<Cursor> { return CursorLoader( activity, Uri.parse(JournalerProvider.URL_NOTES), null, null, null, null ) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) loaderManager.initLoader( 0, null, loaderCallback ) } override fun onResume() { super.onResume() loaderManager.restartLoader(0, null, loaderCallback) val btn = view?.findViewById
<FloatingActionButton>(R.id.new_item) btn?.let { animate(btn, false) } } }
We initialize LoaderManager by calling the LoaderManager member of our Fragment. The two crucial methods we execute are as follows:
- initLoader(): This ensures a loader is initialized and active
- restartLoader(): This starts a new or restarts an existing loader instance
Both methods accept the loader ID and bundle data as arguments and the LoaderCallbacks<Cursor> implementation, which provides the following three methods to override:
- onCreateLoader(): This instantiates and returns a new loader instance for the ID we provided
- onLoadFinished(): This is called when a previously created loader has finished loading
- onLoaderReset(): This is called when a previously created loader is being reset, and, because of that, making its data unavailable