Android: Fragments, Sqlite And Loaders
Solution 1:
I think implementing content provider is a good idea, no matter that data will not be accessible outside of the application. It provides very modern interface and from my experience makes your application error prone to database locking issues and other db-specific problems.
I've implemented it in my latest project and I was very happy to use it.
Solution 2:
I recommend OrmLite library, a lightweight Object Relational Mapping that can work for Android. This library will make your life easier . You don't need to create or update database by hand, you don't need to focus on managing database connection, all queries select, insert, update will be easier with a DAO
approach (usually you don't need to write your own sql query) and a lot of features. They have some examples that you can start with.
And if you want to use the Loader
, there is a ORMLite Extras , additional functionality for ORMLite available on github
(You can use the support package which is compatible with support android library). Here is an example usage on my previous project:
publicclassEventsFragmentextendsFragmentimplementsLoaderCallbacks<Cursor>{
privatestatic final int LOADER_ID = EventsFragment.class.getName().hashCode();
@OverridepublicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
@OverridepublicViewonCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layoutRoot = inflater.inflate(
R.layout.fragment_events, null);
lvEvents = (ListView) layoutRoot.findViewById(R.id.lvEvents);
adapter = newEventAdapter(getActivity(), null, null);
lvEvents.setAdapter(adapter);
return layoutRoot;
}
@OverridepublicLoader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
try {
PreparedQuery<Event> query = getDatabaseHelper().getEventDao().getQuery();
returngetDatabaseHelper().getEventDao().getSQLCursorLoader(query );
} catch (Exception e) {
e.printStackTrace();
}
returnnull;
}
@OverridepublicvoidonLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
adapter.swapCursor(cursor);
try {
adapter.setQuery(getDatabaseHelper().getEventDao().getQuery());
} catch (SQLException e) {
e.printStackTrace();
}
}
@OverridepublicvoidonLoaderReset(Loader<Cursor> arg0) {
adapter.swapCursor(null);
}
privateOrmliteDatabaseHelpergetDatabaseHelper(){
return ((MainActivity)getActivity()).getDatabaseHelper();
}
}
The adapter
publicclassEventAdapterextendsOrmliteCursorAdapter<Event>{
publicEventAdapter(Context context, Cursor c, PreparedQuery<Event> query) {
super(context, c, query);
}
@OverridepublicvoidbindView(View itemView, Context context, Event item) {
TextViewtvEventTitle= (TextView) itemView.findViewById(R.id.tvEventTitle);
TextViewtvEventStartDate= (TextView) itemView.findViewById(R.id.tvEventStartDate);
tvEventTitle.setText(item.getTitle());
tvEventStartDate.setText(item.getFormatStartDate());
}
@Overridepublic View newView(Context context, Cursor arg1, ViewGroup arg2) {
LayoutInflaterinflater= LayoutInflater.from(context);
ViewretView= inflater.inflate(R.layout.event_item_row, arg2, false);
return retView;
}
}
And a custom Dao which provides PreparedQuery
for cursor adapter above:
publicinterfaceIEventDaoextendsDao<Event, Integer>{
PreparedQuery<Event> getQuery()throws SQLException;
OrmliteCursorLoader<Event> getSQLCursorLoader(Context context, PreparedQuery<Event> query)throws SQLException;
}
publicclassEventDaoextendsAndroidBaseDaoImpl<Event, Integer> implementsIEventDao{
publicEventDao(ConnectionSource connectionSource)throws SQLException {
super(connectionSource, Event.class);
}
publicEventDao(ConnectionSource connectionSource,
DatabaseTableConfig<Event> tableConfig)throws SQLException {
super(connectionSource, tableConfig);
}
@Overridepublic PreparedQuery<Event> getQuery()throws SQLException{
return queryBuilder().prepare();
}
}
Hope this can help!
Solution 3:
You can extend Loader class in order to perform other Async work such as loading directly from your DB.
Here is an example of that
Edit: added A better example of Loader usage.
Finally managed to find the tutorial that really helped me understand how things work.
By extending the loader class you can avoid messing with content observers and its quite easy to implement (at last) the modifications that need to be taken in your code are
- Add implementation of
LoaderManager.LoaderCallbacks<D>
, whereD
is your data list (snippet 1) - Create your loader class, copy snippet 2, and add the loading of your data from the DB
- Finally call the loaders 1 call for init and then for refreshing the restart call. (snippet 2 & 3)
Snippet 1: How to "link" the loader with your fragment:
publicstaticclassAppListFragmentextendsListFragmentimplementsLoaderManager.LoaderCallbacks<List<SampleItem>> {
publicLoader<List<SampleItem>> onCreateLoader(int id, Bundle args) {
//...returnnewSampleLoader (getActivity());
}
publicvoidonLoadFinished(Loader<List<SampleItem>> loader, List<SampleItem> data) {
// ...
mAdapter.setData(data);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
// ...
}
publicvoidonLoaderReset(Loader<List<SampleItem>> loader) {
// ...
mAdapter.setData(null);
// ...
}
/* ... */
}
Snippet 2: Schema of your custom loader: (I have double commented the observer things as I believe that it is quite difficult to implement it from the beginning and you can simple recall the loader without messing with that automated refreshing)
publicclassSampleLoaderextendsAsyncTaskLoader<List<SampleItem>> {
// We hold a reference to the Loader’s data here.privateList<SampleItem> mData;
publicSampleLoader(Context ctx) {
// Loaders may be used across multiple Activitys (assuming they aren't// bound to the LoaderManager), so NEVER hold a reference to the context// directly. Doing so will cause you to leak an entire Activity's context.// The superclass constructor will store a reference to the Application// Context instead, and can be retrieved with a call to getContext().super(ctx);
}
/****************************************************//** (1) A task that performs the asynchronous load **//****************************************************/@OverridepublicList<SampleItem> loadInBackground() {
// This method is called on a background thread and should generate a// new set of data to be delivered back to the client.List<SampleItem> data = newArrayList<SampleItem>();
// TODO: Perform the query here and add the results to 'data'.return data;
}
/********************************************************//** (2) Deliver the results to the registered listener **//********************************************************/@OverridepublicvoiddeliverResult(List<SampleItem> 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<SampleItem> 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);
}
}
/*********************************************************//** (3) Implement the Loader’s state-dependent behavior **//*********************************************************/@OverrideprotectedvoidonStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.deliverResult(mData);
}
// Begin monitoring the underlying data source.////if (mObserver == null) {////mObserver = new SampleObserver();// TODO: register the observer////}//// takeContentChanged() can still be implemented if you want //// to mix your refreshing in that mechanism 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();
}
}
@OverrideprotectedvoidonStopLoading() {
// 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.
}
@OverrideprotectedvoidonReset() {
// 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) {// TODO: unregister the observer//// mObserver = null;////}
}
@OverridepublicvoidonCanceled(List<SampleItem> 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);
}
privatevoidreleaseResources(List<SampleItem> 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.
}
/*********************************************************************//** (4) Observer which receives notifications when the data changes **//*********************************************************************/// NOTE: Implementing an observer is outside the scope of this post (this example// uses a made-up "SampleObserver" to illustrate when/where the observer should // be initialized). // The observer could be anything so long as it is able to detect content changes// and report them to the loader with a call to onContentChanged(). For example,// if you were writing a Loader which loads a list of all installed applications// on the device, the observer could be a BroadcastReceiver that listens for the// ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular // Loader whenever the receiver detects that a new application has been installed.// Please don’t hesitate to leave a comment if you still find this confusing! :)////private SampleObserver mObserver;
}
Snippet 3: How to call the loader for the first time (ONLY)
// Initialize a Loader with an id. If the Loader with this id is not // initialized before
getLoaderManager().initLoader(LOADER_ID, null, this);
Snippet 4: For refreshing data (recalling the query)
// Check if the loader exists and then restart it.if (getLoaderManager().getLoader(LOADER_ID) != null)
getLoaderManager().restartLoader(LOADER_ID, null, this);
Reference:
- Snippet 1 : usage of loader extracted from here
- Snippet 2 : here for more info and logic read throughout the hole article
- Snippet 3 & 4: are just loader usage.
Full code of these is also uploaded by the creator on github
Solution 4:
If your database contains thousands of records consider madlymad's answer
If not keep it stupid and simple, use SQLiteOpenHelper
and create a method that returns you your data as array of strings or define your one objects.
Also use custom/regular CursorAdapter or ArrayAdapter.
Solution 5:
I use the SQLiteOpenHelper to create my database. I have made Java classes for all the tables and when I get the data from my database I put it in an ArrayList. The ArrayList I then load into the adapter of the Listview.
Post a Comment for "Android: Fragments, Sqlite And Loaders"