Search code examples
androidandroid-asynctaskandroid-viewandroid-cursorloader

Android CursorLoader adding views dynamically in specific/certain order


This is probably very odd, but I'm using multiple CursorLoaders in Android to do multiple queries and in the onLoadFinished(), I am adding views like TextViews and ListViews to my layout dynamically based on cursor results like if the cursors were not null. I do get accurate results, but since I'm using AsyncTaskLoader (CursorLoader), the cursor results don't come in at the same time and the results are not added in the correct order. I previously used a static layout and added views at indices and did view.setVisiblity(View.GONE) based on the results, but it was just too much and too confusing because I have like 32 views. Plus it seemed weird because I don't think the user wants to see all of those views going away and moving up and down based on AsyncTaskLoader results. How can I get the views in the correct order I want them in without having a bunch of boolean variables? I looked into LayoutInflater but that requires indices as well, but I'm not sure that will help me. The problem with indices for me is that in cursorloader ID 1:

    view.addView(v, 1);
    view.addView(v, 2);

might not get executed until the cursorloader with ID 2 finishes with:

    view.addView(v, 3);
    view.addView(v, 4);

If cursorLoader ID 1 doesn't get executed and ID 2 does, then there is missing space and I have to do a ton of view.setVisibility(View.GONE) if I use static XML views and do not dynamically add them.

In the code I'm doing something like this currently:

     @Override
     public void onLoadFinished(android.support.v4.content.Loader<Cursor> cursorLoader, Cursor cursor) { 
     switch (cursorLoader.getId())
     {
           case 0:
                   if (cursor != null && cursor.moveToFirst()) {
                      ..
                      title = new TextView(this);
                      ...
                      mainLinearLayout.addView(title, 1);
                   }
                   break;
           case 1:
                    if (cursor != null && cursor.moveToFirst()) {
                      ..
                   title2 = new TextView(this);
                   mainLinearLayout.addView(title2, 2);
                   break;
           default:
                   ...
    }

}

I also read somewhere online that it is better to use a service instead of cursorloader if you want to do queries on the background thread and have them finish in a certain order, but I have not heard that advice anywhere else or seen any examples doing queries in services. They all use CursorLoader. Is this advice necessarily true? Sounds a bit sketchy.

By the way, I am using the CursorLoader implementation without a content provider given at CursorLoader usage without ContentProvider


Solution

  • How can I get the views in the correct order I want them in without having a bunch of boolean variables?

    You do need some sort of status control in order to make the views appear in order. I would delegate the view construction/addition to a control class that will have all the information required to make the correct view and in the right order no matter how the loaders finished their jobs.

    public class ViewDispatcher {
        public SparseArray<Status> mLoadStatuses = new SparseArray<Status>();
        public SparseArray<Cursor> mDataCursors = new SparseArray<Cursor>();
    
        // you'll call this method for each of the loaders, in the order they should be. The ids should be incremental
        public void registerLoader(int loaderId) {
            mLoadStatuses.put(loaderId, Status.INITIAL);
        }
    
        // called when one of the loaders finishes its job
        public void onLoadComplete(int loaderId, Cursor data) {
             mDataCursors.put(loaderId, data);
             boolean current = true;
             mLoadStatuses.put(loaderId, Status.LOADED);
             if (loaderId == firstLoaderId) {
                // the first loader it's done and we should start the view creation right away
                buildView(loaderId, mainLayout, true);
                mLoadStatuses.put(loaderId, data, Status.FULLY_BUILT);          
             } else {
               // implement a priority system, a view construction will be triggered ONLY 
               // if the previous loader has finished loading data and its view is in place
               // I'm assuming that the Loaders have consecutive ids
               if (mLoadStatuses.get(loaderId - 1) != null && mLoadStatuses.get(loaderId - 1) == Status.FULLY_BUILT) {
                   buildView(loaderId, data, mainLayout, true); 
                   mLoadStatuses.put(loaderId, Status.FULLY_BUILT);    
                } else {
                   current = false;
                } 
             }  
             // we'll also need to implement a buddy system. When a loader is done loading and its view
             // is created we must check to see if we don't have other loaders after this current one that have finished loading 
             // but couldn't construct their view because this current loader didn't finished at that moment
             // get the next loader
             int next = loaderId + 1;
             while(current && next < totalNumberOfLoaders && mLoadStatuses.get(next) == Status.LOADED) {
                // continue to build views
                buildView(next, mDataCursors.get(loaderId), mainLayout, true);              
                mLoadStatuses.put(next, Status.FULLY_BUILT);
                next++; 
             } 
        }  
    
        // this will build the appropriate view, and optionally attach it
        public void buildView(int loaderId, Cursor data, view mainLayout, boolean attach) {
            // build the view for this specific Loader
        }
    
    }
    
    public enum Status {
        INITIAL, LOADED, FULLY_BUILT 
    }
    

    I hope I'm not missing something obvious as I wrote that without any tests. To use it, you'll first call the registerLoader() method for all loaders in the order you need them to be and in the onLoadComplete() callback of the LoaderCallbacks call ViewDispatcher.onLoadComplete().

    I also read somewhere online that it is better to use a service instead of cursorloader if you want to do queries on the background thread and have them finish in a certain order, but I have not heard that advice anywhere else or seen any examples doing queries in services.

    You've probably read about IntentService which can be made to follow a queue through the order of the Intents it receives. But, I don't see how this would help you as it would just add problems. For one you use Cursors as the data holders that you would need to pass back and you need to create views which the IntentService can't do(it will need to make the Activity create them through various communication ways, this is unnecessary work from my point of view).