Currently i'm starting in Android development and i want to learn how to develop apps and apply good rules and recommendations. Somewhere i read that the right thing is to use CursorLoader to load lists to load data asynchronous in the view. Previously, thanks to a tutorial i created a simple app that shows a list of data in a SQLite database table, with a custom Adapter that i extended from SimpleCursorAdapter to show different icons basing on the value of a certain field of each row of the query (cursor). The code of my custom adapter is the next:
public class MarcaCursorAdapter extends SimpleCursorAdapter {
private Context ctx;
private Cursor cur;
public MarcaCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to, 0);
this.ctx = context;
this.cur = c;
// TODO Auto-generated constructor stub
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null)
convertView = View.inflate(ctx, R.layout.template_marcas, null);
View row = convertView;
ImageView imgRk = (ImageView)convertView.findViewById(R.id.tipoRanking);
//Dependiendo del ranking, cargaremos la imagen de mario o de wario
if(cur.getInt(3) % 2 == 0)
imgRk.setImageResource(R.drawable.btn1_pressed);
else
imgRk.setImageResource(R.drawable.btn1_focus);
return row;
} }
And the content of template_marcas.xml is the next:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:background="#002EB8"
android:paddingTop="20dp" >
<TextView
android:id="@+id/nomMarca"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:textColor="#FFFFFF" />
<TextView
android:id="@+id/textView69"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/nomMarca"
android:layout_toRightOf="@+id/nomMarca"
android:text=" es la numero "
android:textColor="#FFFFFF" />
<TextView
android:id="@+id/rankMarca"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView69"
android:layout_toRightOf="@+id/textView69"
android:textColor="#FFFFFF" />
<ImageView
android:id="@+id/tipoRanking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/textView69"
android:layout_marginRight="56dp" /></RelativeLayout>
So, in order to use CursorLoader and ContentProvider, i converted my normal class that had a method to query the table to get a cursor of that table, i extended from ContentProvider and registered in the manifest file, the class is the next:
public class MarcaDAO extends ContentProvider {
private ConnHandler conn;
SQLiteDatabase dbObj;
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean onCreate() {
conn = new ConnHandler(getContext());
dbObj = conn.getDb();
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor cur = dbObj.query("MARCA", projection, null, null, null, null, null);
return cur;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}}
conn and dbObj objets are used to manage connection, those object works. And finally, i have the Activity which implements LoaderCallbacs with the overriden methods and initializing the CursorLoader:
public class MarcaActivity extends Activity implements
LoaderManager.LoaderCallbacks {
//MarcaDAO dao;
MarcaCursorAdapter crsAdap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_marca);
final ListView lstMarcas = (ListView)findViewById(R.id.lstMarcas);
//Sacamos los datos de las marcas para mostrarlos en el listview
//dao = new MarcaDAO(this);
//Cursor cur = dao.getList();
getLoaderManager().initLoader(0, null, this);
crsAdap = new MarcaCursorAdapter(this, R.layout.template_marcas, null ,
new String[]{"hip_nombre", "hip_ranking"}, new int[]{R.id.nomMarca, R.id.rankMarca});
lstMarcas.setAdapter(crsAdap);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.marca, menu);
return true;
}
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(this, Uri.parse("content://com.example.crudapp.marcaData"),
new String[]{"_id", "hip_nombre", "hip_codigo", "hip_ranking"}, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
crsAdap.swapCursor(arg1);
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
crsAdap.swapCursor(null);
}}
But, when i run the application (4.2.2) i get a NullPointerException in the method getView of MarcaCursorAdapter in this line:
if(cur.getInt(3) % 2 == 0)
Because the cursor row values are null, and if i remove this code, the list loads the exact number of rows but with no values (the textviews nomMarca and rankMarca are empty) so i don't know what i'm doing wrong. Previously it worked without content provider and without cursorloader but i wanna develop good code so i changed my classes.
Regards.
UPDATE: By the way, i forgot to say, if in the Activity instead using MarcaCursorAdapter i just use SimpleCursorAdapter (losing the customization of my method getView) the list loads without problems and showing the data, so i think that my problem is in the way that i coded my MarcaCursorAdapter class, but i don't know what's the mistake.
Regards again.
Because the cursor row values are null,
The Cursor
values aren't null(or at least this isn't the reason for the error) it's the cur
reference that is null. When you first initialize the adapter you do it with a null Cursor
(at this point cur
is null). When your loader does its job and loads the data you call swapCursor()
and the adapter will start building rows, unfortunately your cur
reference will continue to be null as it doesn't get magically updated with the new Cursor
reference.
By the way, i forgot to say, if in the Activity instead using MarcaCursorAdapter i just use SimpleCursorAdapter (losing the customization of my method getView) the list loads without problems and showing the data,
This is happening because of your implementation of the getView()
method. First of all, for Cursor based adapter you'd want to override the two method responsible for building a row newView()
(create the actual row view) and bindView()
(bind the previously built row view with Cursor data). If all you want to do is change that image resource then you have two choices:
ViewBinder
on your adapter(recommended).Extend SimpleCursorAdapter
and override only it's bindView()
method:
//...
@Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor); // let the default implementation populate our views
// change the image
ImageView imgRk = (ImageView)view.findViewById(R.id.tipoRanking);
//Dependiendo del ranking, cargaremos la imagen de mario o de wario
if(cursor.getInt(3) % 2 == 0) {
imgRk.setImageResource(R.drawable.btn1_pressed);
} else {
imgRk.setImageResource(R.drawable.btn1_focus);
}
}