2

I'm making an application which shows a list of zipcodes( in ListView) and when I click on it, it shows me detailed weather report using OpenWeather API. The list of zipcodes are stored in SQLite database and to retrieve data from it, I'm using AsyncTaskLoader. The data is shown properly. The problem is during configuration change, loadinBackground() method is being called which shouldn't happen.

public class DataLoader extends AsyncTaskLoader<List<RowItem>> 
{
Context activityContext;
private String TAG="DataLoader";
public DataLoader(Context context)
{
    super(context);
    this.activityContext = context;
}

@Override
public List<RowItem> loadInBackground()
{
    Log.v(TAG, "loadinBackground() IN");
    List<RowItem> rowItems = new ArrayList<RowItem>();
    DbReaderHelper mDbHelper = new DbReaderHelper(activityContext);
    SQLiteDatabase db = mDbHelper.getReadableDatabase();
    String[] columns = {DbReaderHelper.COLUMN_CITYNAME,DbReaderHelper.COLUMN_ZIPCODE};
    Cursor cur =  db.query(DbReaderHelper.TABLE_NAME, columns,null,null,null,null,null);
    cur.moveToFirst();
    while(!cur.isAfterLast())
    {
        String city = cur.getString(cur.getColumnIndex(DbReaderHelper.COLUMN_CITYNAME));
        String zipcode = cur.getString(cur.getColumnIndex(DbReaderHelper.COLUMN_ZIPCODE));
        rowItems.add(new RowItem(city,Integer.parseInt(zipcode)));
        cur.moveToNext();
    }
    cur.close();    // release all the resources
    db.close();
    mDbHelper.close();
    Log.v(TAG, "loadinBackground() OUT");
    return rowItems;
}
}

Here is the implementation of Loader Callbacks

public class ListFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<RowItem>>
{
private TextView emptyListTV;
private EditText zipcodeET;
private CustomAdapter customAdapter;
private List<RowItem> rowItems;
private ListView listView;
private final String TAG = "ListFragment";
private boolean fromRemoveItemFromListView = false;     // TODO check this again
private final int  LOADER_ID = 0x0;

public ListFragment()
{
    // empty constructor
}

@Override
public void onCreate(Bundle savedInstanceState)
{
    Log.v(TAG,"onCreate() IN");
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
    Log.v(TAG,"onCreate() OUT");
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    // Inflate the layout for this fragment
     View view = inflater.inflate(R.layout.fragment_list, container, false);
     initializeLayoutViews(view);
    return view;
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
    getLoaderManager().initLoader(LOADER_ID,null,this).forceLoad();
}

@Override
public Loader<List<RowItem>> onCreateLoader(int id, Bundle args)
{
    Toast.makeText(getActivity(),"onCreateLoader()",Toast.LENGTH_SHORT).show();
    return new DataLoader(getActivity());
}

@Override
public void onLoadFinished(Loader<List<RowItem>> loader, List<RowItem> rowItems)
{
    Toast.makeText(getActivity(),"onLoadFinished",Toast.LENGTH_SHORT).show();
    customAdapter.setData(rowItems);
    customAdapter.notifyDataSetChanged();
    setMessageIfListEmpty();
}

@Override
public void onLoaderReset(Loader<List<RowItem>> loader)
{
    Toast.makeText(getActivity(),"onLoaderReset()",Toast.LENGTH_SHORT).show();
    customAdapter.setData(new ArrayList<RowItem>());
    customAdapter.notifyDataSetChanged();
}
}

Here is my Logs when orientation changes

01-31 22:07:12.532  15019-15019/com.siddhant.myweatherapp D/AbsListView﹕ onDetachedFromWindow
01-31 22:07:12.583  15019-15019/com.siddhant.myweatherapp I/PersonaManager﹕ getPersonaService() name persona_policy
01-31 22:07:12.583  15019-15019/com.siddhant.myweatherapp V/MainActivity﹕ onCreate() IN
01-31 22:07:12.653  15019-15019/com.siddhant.myweatherapp V/MainActivity﹕ onStart() IN
01-31 22:07:12.733  15019-15019/com.siddhant.myweatherapp D/AbsListView﹕ Get MotionRecognitionManager
01-31 22:07:12.743  15019-15019/com.siddhant.myweatherapp V/ListFragment﹕ onLoadFinished() IN
01-31 22:07:12.743  15019-15019/com.siddhant.myweatherapp V/ListFragment﹕ onLoadFinished() OUT
01-31 22:07:12.743  15019-15019/com.siddhant.myweatherapp V/ListFragment﹕ onLoadFinished() IN
01-31 22:07:12.743  15019-15019/com.siddhant.myweatherapp V/ListFragment﹕ onLoadFinished() OUT
01-31 22:07:12.743  15019-15019/com.siddhant.myweatherapp V/MainActivity﹕ onStart() OUT
01-31 22:07:12.753  15019-15019/com.siddhant.myweatherapp V/MainActivity﹕ OnResume() IN
01-31 22:07:12.753  15019-15522/com.siddhant.myweatherapp V/DataLoader﹕ loadinBackground() IN
01-31 22:07:12.753  15019-15019/com.siddhant.myweatherapp V/MainActivity﹕ onResume() OUT
01-31 22:07:12.803  15019-15019/com.siddhant.myweatherapp E/ViewRootImpl﹕ sendUserActionEvent() mView == null
01-31 22:07:12.813  15019-15019/com.siddhant.myweatherapp V/MainActivity﹕ onCreateOptionsMenu() IN
01-31 22:07:12.813  15019-15019/com.siddhant.myweatherapp V/MainActivity﹕ onCreateOptionsMenu() OUT
01-31 22:07:12.953  15019-15522/com.siddhant.myweatherapp V/DataLoader﹕ loadinBackground() OUT
01-31 22:07:12.963  15019-15019/com.siddhant.myweatherapp V/ListFragment﹕ onLoadFinished() IN
01-31 22:07:12.963  15019-15019/com.siddhant.myweatherapp V/ListFragment﹕ onLoadFinished() OUT

Thanks,

user2430771
  • 1,326
  • 4
  • 17
  • 33

3 Answers3

0

You are calling forceLoad() on the loader returned by initLoader(). This will cause the loader to reload the data even if it has already retrieved that data before. You shouldn't need to call this method, just call initLoader().

Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • Thanks for response.When I do not add forceLoad(), my loader callbacks are not called at all. Can you tell me why it happens? – user2430771 Feb 01 '15 at 06:26
0

forceLoad() is not called automatically but is needed to initiate the loading of data.

However forceLoad() should be done within onStartLoading() and not called on initLoader()

AsyncTaskLoaders are deprecated from API Level 28(Android Pie) onwards.

pcodex
  • 1,812
  • 15
  • 16
0

Unlike AsyncTask which auto-runs the background task when you call myAsyncTask.execute(), the AsyncTaskLoader doesn't auto run the background thread when you call getSupportLoaderManager().initLoader(MY_LOADER_ID, null, this); to enforce it to run the background thread you need to explicitly call forceLoad() on the Loader object by two ways:

First: by method chain when initializing/activating a loader with the LoaderManager with getSupportLoaderManager().initLoader(MY_LOADER_ID, null, this).forceLoad();

Second: In the custom loader class onStartLoading() callback which is auto-triggered by getSupportLoaderManager().initLoader(...)

@Override
protected void onStartLoading() {
    super.onStartLoading();
    forceLoad(); // call loadInBackground()
}

Apparently for configuration changes, both methods will call loadInBackground() method because onCreate() method will be triggered on screen rotation followed by getSupportLoaderManager().initLoader(...) and forceLoad() will be called accordingly, but implicitly you can utilize the second method to stop calling loadInBackground() unnecessarily by using deliverResult() method which delivers the result of the previous load to the registered listener's onLoadFinished() method which in turn allows us to skip loadInBackground() call.

So, what you need to change in your code:

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
    getLoaderManager().initLoader(LOADER_ID, null, this);
}

And here is the loader

public class DataLoader extends AsyncTaskLoader<List<RowItem>> {

    Context activityContext;
    private String TAG="DataLoader";
    private List<RowItem> rowItems;

    public DataLoader(Context context){
        super(context);
        this.activityContext = context;
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        if (rowItems != null) {
            deliverResult(rowItems); // skip loadInBackground() call
        } else {
            forceLoad(); // call loadInBackground()
        }
    }


    @Override
    public List<RowItem> loadInBackground(){
        Log.v(TAG, "loadinBackground() IN");
        rowItems = new ArrayList<RowItem>();
        DbReaderHelper mDbHelper = new DbReaderHelper(activityContext);
        SQLiteDatabase db = mDbHelper.getReadableDatabase();
        String[] columns = {DbReaderHelper.COLUMN_CITYNAME,DbReaderHelper.COLUMN_ZIPCODE};
        Cursor cur =  db.query(DbReaderHelper.TABLE_NAME, columns,null,null,null,null,null);
        cur.moveToFirst();
        while(!cur.isAfterLast())
        {
            String city = cur.getString(cur.getColumnIndex(DbReaderHelper.COLUMN_CITYNAME));
            String zipcode = cur.getString(cur.getColumnIndex(DbReaderHelper.COLUMN_ZIPCODE));
            rowItems.add(new RowItem(city,Integer.parseInt(zipcode)));
            cur.moveToNext();
        }
        cur.close();    // release all the resources
        db.close();
        mDbHelper.close();
        Log.v(TAG, "loadinBackground() OUT");
        return rowItems;
    }
}

It worth to mention that Loaders have been deprecated as of Android P (API 28).

Zain
  • 37,492
  • 7
  • 60
  • 84