Using Loaders with sqlite query that returns object

老子叫甜甜 提交于 2020-01-05 04:04:25

问题


Here's what I've been doing all this while to get data from my database - my fragment will call a dbhelper which will run the query and pass the resulting data in an object.

I should have used a loader so that the querying is not done in the UI thread, but previously I didn't bother since my app's database is very small. Moving forward, this was a bad idea since my app has grown larger and so do the database. So now I'm reading all I can about Loaders including CursorLoader and custom loaders, but I still have problem implementing it in my app.

So here's my current code (simplified, just to show the relevant parts):

public class CategoryEdit extends Fragment{

private DbControl dbc;
private ObjectCategory objCategory;
private int categoryID = 3;
private EditText etName;

@Override
    public void onActivityCreated(Bundle savedInstanceState) {
        dbc = new DbControl(getActivity());
        try{
            dbc.open();
            objCategory = new ObjectCategory();
            objCategory = dbc.getCategoryData(categoryID);
            dbc.close();
        }catch (SQLException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }

        etName.setText(objCategory.name);
    }
}

public class DbControl {

    public static final String FILE_NAME = "DbControl.java";
    private SQLiteDatabase database;
    private MySQLiteHelper dbHelper;

    public DbControl(Context context) {
        dbHelper = new MySQLiteHelper(context);
    }

    public void open() throws SQLException {
        database = dbHelper.getWritableDatabase();
    }

    public void close() {
        dbHelper.close();
    }

    public ObjectCategory getCategoryData(int itemID) throws Exception{
        ObjectCategory objCategory = new ObjectCategory();
        Cursor cursor = database.query(MySQLiteHelper.TABLE_CATEGORY, new String[] {"name"},
                "id = " + itemID, null, null, null, null);
        if (cursor!=null && cursor.getCount()>0 && cursor.moveToFirst()) {
            objCategory.name = cursor.getString(cursor.getColumnIndex("name"));
        }
        return objCategory;
    }
}

Can anyone point me in the right direction, in implementing Loader the right way in my case here? If possible, I don't want to alter the codes inside class DbControl too much - especially the return data type of the functions inside it.

Btw amongst the tutorials about loaders that I've read is:

  • http://www.recursiverobot.com/post/60331340133/very-simple-example-of-a-loader-and-loadermanager - but this was written more than 3 years ago and it uses Runnable to execute getSupportLoaderManager, and I've not seen other examples/tutorials that does this, so I'm afraid this article is outdated. Furthermore it uses CursorLoader, which from my understanding can only return cursor.
  • http://www.grokkingandroid.com/using-loaders-in-android/ - this seems to deal with CursorLoader too

Anyway here's what I've done so far:

public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {

@Override
    public Loader<ObjectCategory> onCreateLoader(int id, Bundle args){
        try{
            dbc.open();
            objCategory = new ObjectCategory();
            objCategory = dbc.getCategoryData(categoryID);
            dbc.close();
        }catch (SQLException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }

        return objCategory; //this returns error, it says required Loader<ObjectCategory> instead of objCategory, but I'm not sure how to do that here
    }

@Override
    public void onLoadFinished(Loader<ObjectCategory> cursorLoader, ObjectCategory cursor) {
//Since onCreateLoader has error above, I'm not sure how to implement this part
    }

@Override
    public void onLoaderReset(Loader<ObjectCategory> cursorLoader) {
        //Can I just leave this empty?
    }
}

回答1:


Can anyone point me in the right direction, in implementing Loader the right way in my case here?

You have to use an AsyncTaskLoader like this:

public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {

    @Override
    public Loader<ObjectCategory> onCreateLoader(int id, Bundle args) {
        return new AsyncTaskLoader<ObjectCategory>(getActivity()) {
            @Override
            public ObjectCategory loadInBackground() {
                try {
                    dbc.open();
                    objCategory = new ObjectCategory();
                    objCategory = dbc.getCategoryData(categoryID);
                    dbc.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return objCategory;
            }

            @Override
            protected void onStartLoading() {
                forceLoad();
            }
        };

    }

    @Override
    public void onLoadFinished(Loader<ObjectCategory> loader, ObjectCategory data) {
        //do some stuff here
    }

    @Override
    public void onLoaderReset(Loader<ObjectCategory> loader) {
        //Yes, you can leave it empty
    }
}



回答2:


I would say - don't use Loaders. I used CursorLoader in my app when I was new to Android, and it turned out to be a bad decision.

If CursorLoader is sufficient for your needs, then you're alright (though your Activities and Fragments will become polluted with DB related logic), but once you try to implement your own Loader, that should encapsulate DB related logic and just return a constructed object, you'll inevitably bump into a million issues with LoaderManager framework. @CommonsWare wrote a nice article about it - read it before you integrate Loaders into your app.

Nowadays I usually use "managers" for loading data from SQLite or internet. The general form of my "manager" is like this (please note that these "managers" are not Singletons):

public class TutorialManager {

    interface TutorialManagerListener {
        void onDataFetchCompleted(SomeData data);
    }
    private BackgroundThreadPoster mBackgroundThreadPoster;
    private MainThreadPoster mMainThreadPoster;


    private Set<TutorialManagerListener> mListeners = new HashSet<>(1);

    public TutorialManager(@NonNull BackgroundThreadPoster backgroundThreadPoster,
                           @NonNull MainThreadPoster mainThreadPoster) {
        mBackgroundThreadPoster = backgroundThreadPoster;
        mMainThreadPoster = mainThreadPoster;
    }

    @UiThread
    public void registerListener(@NonNull TutorialManagerListener listener) {
        mListeners.add(listener);
    }

    @UiThread
    public void unregisterListener(@NonNull TutorialManagerListener listener) {
        mListeners.remove(listener);
    }

    @UiThread
    private void notifyFetchCompleted(SomeData data) {
        for (TutorialManagerListener listener : mListeners) {
            listener.onDataFetchCompleted(data);
        }
    }

    /**
     * Call to this method will fetch data on background thread and then notify all registered
     * listeners about new data on UI thread.
     */
    public void fetchData() {
        mBackgroundThreadPoster.post(new Runnable() {
            @Override
            public void run() {

                // logic that loads the data on background thread goes here
                // final SomeData data = ... ;

                mMainThreadPoster.post(new Runnable() {
                    @Override
                    public void run() {
                        notifyFetchCompleted(data);
                    }
                });
            }
        });
    }
}

Implementation of MainThreadPoster:

/**
 * This class should be used in order to execute {@link Runnable} objects on UI thread
 */
public class MainThreadPoster {

    private final Handler mMainHandler;

    public MainThreadPoster() {
        mMainHandler = new Handler(Looper.getMainLooper());
    }

    /**
     * Post {@link Runnable} for execution on main thread
     */
    public void post(Runnable runnable) {
        mMainHandler.post(runnable);
    }

}

Implementation of BackgroundThreadPoster is up to you.I usually use the following simple implementation (unless some fine tuning is required):

/**
 * This class should be used in order to execute {@link Runnable} objects on background threads
 */
public class BackgroundThreadPoster {

    private final ExecutorService mBackgroundExecutor;

    public BackgroundThreadPoster() {
        mBackgroundExecutor = Executors.newCachedThreadPool();
    }

    /**
     * Post {@link Runnable} for execution on a random background thread
     */
    public void post(Runnable runnable) {
        mBackgroundExecutor.execute(runnable);
    }

}

I inject MainThreadPoster and BackgroundThreadPoster objects into "managers" in order to:

  1. Make "managers" unit-testable
  2. Perform centralized management and optimization of background threading across entire application


来源:https://stackoverflow.com/questions/40365548/using-loaders-with-sqlite-query-that-returns-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!