Question

I'm having problems using AsyncTaskLoader. This is my first attempt populating a ListView from a SQLite database using a loader.

Everything seems ok, when I rotate the screen the data is cached and no query is done again. But when I press the home button and launch my app again, the data is loaded again.

Note: Usuario means User, so I'm populating the ListView with a list of users.

public class Main extends SherlockFragmentActivity
        implements LoaderManager.LoaderCallbacks<ArrayList<Usuario>> {
    UsuarioAdapter adapter;
    ListView listView;
    Database db;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        listView = (ListView) findViewById(R.id.lista);
        db       = new Database(this);
        adapter  = new UsuarioAdapter(this, new ArrayList<Usuario>());
        listView.setAdapter(adapter);
        getSupportLoaderManager().initLoader(0, null, this);
    }

    @Override
    public Loader<ArrayList<Usuario>> onCreateLoader(int id, Bundle args) {
        return new UsuariosLoader(this, db);
    }

    @Override
    public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
                           ArrayList<Usuario> usuarios) {
       //adapter.notifyDataSetChanged();
      listView.setAdapter(new UsuarioAdapter(this, usuarios));
      // ((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
    }

    @Override
    public void onLoaderReset(Loader<ArrayList<Usuario>> loader) {
        listView.setAdapter(null);
    }
}

// THE LOADER
class UsuariosLoader extends AsyncTaskLoader<ArrayList<Usuario>> {
    private ArrayList<Usuario> usuarios;
    private Database db;

    public UsuariosLoader(Context context, Database db) {
        super(context);
        this.db = db;
    }

    @Override
    protected void onStartLoading() {
        if (usuarios != null) {
          deliverResult(usuarios); // Use the cache
        }
       forceLoad();
    }

    @Override
    protected void onStopLoading() {
        // The Loader is in a stopped state, so we should attempt to cancel the
        // current load (if there is one).
        cancelLoad();
    }

    @Override
    public ArrayList<Usuario> loadInBackground() {              
        db.open();  // Query the database
        ArrayList<Usuario> usuarios = db.getUsuarios();
        db.close();
        return usuarios;
    }

    @Override
    public void deliverResult(ArrayList<Usuario> data) {
        usuarios = data; // Caching
        super.deliverResult(data);
    }

    @Override
    protected void onReset() {
       super.onReset();
       // Stop the loader if it is currently running
       onStopLoading();
       // Get rid of our cache if it exists
       usuarios = null;
    }

    @Override
    public void onCanceled(ArrayList<Usuario> data) {
       // Attempt to cancel the current async load
       super.onCanceled(data);
       usuarios = null;
   }
}

And I think this snippet is not well done. I'm creating a new Adapter instead of updating the data.

    @Override
    public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
                           ArrayList<Usuario> usuarios) {
        //adapter.notifyDataSetChanged();
        listView.setAdapter(new UsuarioAdapter(this, usuarios));
        //((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
    }

Why adapter.notifyDataSetChanged() does not work?

So, basically, my app does not crash but all my data is reloaded again every time I restart the app.

Edit: This is my Adapter code:

    class UsuarioAdapter extends BaseAdapter {
    private ArrayList<Usuario> usuarios;
    private LayoutInflater inflater;

    public UsuarioAdapter(Context context, ArrayList<Usuario> usuarios) {
        this.usuarios = usuarios;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() { return usuarios.size(); }
    @Override
    public Object getItem(int pos) { return usuarios.get(pos); }
    @Override
    public long getItemId(int pos) { return pos; }

    @Override
    public View getView(int pos, View convertView, ViewGroup arg) {
        LinearLayout itemView;
        if (convertView == null) {
            itemView = (LinearLayout) inflater.inflate(R.layout.list_item, null);
        } else {
            itemView = (LinearLayout) convertView;
        }

        ImageView avatar = (ImageView) itemView.findViewById(R.id.avatar);
        TextView nombre  = (TextView) itemView.findViewById(R.id.nombre);
        TextView edad    = (TextView)itemView.findViewById(R.id.edad);

        // Set the image ... TODO
        nombre.setText(usuarios.get(pos).getNombre());
        edad.setText(String.valueOf(usuarios.get(pos).getEdad()));
        return itemView;
    }
}
Was it helpful?

Solution 2

I've found the solution. The onStartLoading was the guilty:

    @Override
    protected void onStartLoading() {
        if (usuarios != null) {
            deliverResult(usuarios); // Use cache
        } else {
            forceLoad();
        }
    }

In my original post forceLoad was always called. It must be in the else branch.

OTHER TIPS

The call to notifyDataSetChanged() won't change the data your adapter is using. You need to update the data the adapter has, then call that method.

NotifyDataSetChanged() will only tell the adapter it needs to create it's views, but it does not change the data. You need to handle that yourself.

In your adapter add:

public void setUsuario(List<Usuario> usuarios) {
    this.usuarios = usuarios;
}

Then in onLoadFinished() call the new method, then notifyDataSetChanged().

listView.getAdapter().setUsuario(usuarios);
listView.getAdapter().notifiyDataSetChanged();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top