Theo Theo - 1 month ago 13
Android Question

Stored image in SQLite is not shown in Gridview

Yesterday I asked this question where I was putting some static data to sqlite via a content provider. Now I want to go one step further. I download some data from a webserver(with Volley) and store them again in SQLite. Next I want to read them with a CursorLoader. However I can only display the titles of the images in the Gridview. So let me start with my code.

MainActivityFragment

public class MainActivityFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>{
static public ArrayList<MyCity> cityList;

private static final String LOG_TAG = MainActivityFragment.class.getSimpleName();
private MyCityAdpapter myCityAdpapter;
private static final int CURSOR_LOADER_ID = 0;
private GridView mGridView;


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

}

public MainActivityFragment() {
// Required empty public constructor
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
Cursor c =
getActivity().getContentResolver().query(MyCityContract.MyCityEntry.CONTENT_URI,
new String[]{MyCityContract.MyCityEntry._ID},
null,
null,
null);
if (c.getCount() == 0){
updateImagesList();
}
// initialize loader
getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
super.onActivityCreated(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
// inflate fragment_main layout
final View rootView = inflater.inflate(R.layout.fragment_main_activity, container, false);

cityList = new ArrayList<>();

// initialize our FlavorAdapter
myCityAdpapter = new MyCityAdpapter(getActivity(), null, 0, CURSOR_LOADER_ID);
// initialize mGridView to the GridView in fragment_main.xml
mGridView = (GridView) rootView.findViewById(R.id.flavors_grid);
// set mGridView adapter to our CursorAdapter
mGridView.setAdapter(myCityAdpapter);

return rootView;

}


@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args){
return new CursorLoader(getActivity(),
MyCityContract.MyCityEntry.CONTENT_URI,
null,
null,
null,
null);
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
myCityAdpapter.swapCursor(data);
}
public void updateImagesList() {


// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(getActivity());

// Request a string response from the provided URL.
JsonArrayRequest jsObjRequest = new JsonArrayRequest(Request.Method.GET, API.API_URL, new Response.Listener<JSONArray>() {

@Override
public void onResponse(JSONArray response) {
cityList.clear();
Log.d(TAG, response.toString());
//hidePD();

// Parse json data.
// Declare the json objects that we need and then for loop through the children array.
// Do the json parse in a try catch block to catch the exceptions
try {

for (int i = 0; i < response.length(); i++) {
//insert images information into the database
JSONObject post = response.getJSONObject(i);

MyCity item = new MyCity();
item.setName(post.getString("title"));
item.setImage(API.IMAGE_URL + post.getString("image"));

ContentValues imageValues = new ContentValues();

imageValues.put(MyCityContract.MyCityEntry._ID, post.getString("id"));
imageValues.put(MyCityContract.MyCityEntry.COLUMN_NAME, post.getString("title"));
imageValues.put(MyCityContract.MyCityEntry.COLUMN_ICON, post.getString("image"));

getActivity().getContentResolver().insert(MyCityContract.MyCityEntry.CONTENT_URI, imageValues);

cityList.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}

// Update list by notifying the adapter of changes
myCityAdpapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
//hidePD();
}
});
queue.add(jsObjRequest);

}

@Override
public void onLoaderReset(Loader<Cursor> loader){
myCityAdpapter.swapCursor(null);
}


}

MyCityAdapter

public class MyCityAdpapter extends CursorAdapter {
private static final String LOG_TAG = MyCityAdpapter.class.getSimpleName();
private Context mContext;
private static int sLoaderID;

public MyCityAdpapter(Context context, Cursor c, int flags,int loaderID) {
super(context, c, flags);
Log.d(LOG_TAG, "MyCityAdpapter");
mContext = context;
sLoaderID = loaderID;
}
public static class ViewHolder {
public final ImageView imageView;
public final TextView textView;

public ViewHolder(View view){
imageView = (ImageView) view.findViewById(R.id.flavor_image);
textView = (TextView) view.findViewById(R.id.flavor_text);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
int layoutId = R.layout.flavor_item;

Log.d(LOG_TAG, "In new View");

View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
view.setTag(viewHolder);

return view;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder viewHolder = (ViewHolder) view.getTag();

Log.d(LOG_TAG, "In bind View");

int versionIndex = cursor.getColumnIndex(MyCityContract.MyCityEntry.COLUMN_NAME);
final String versionName = cursor.getString(versionIndex);
Log.i(LOG_TAG, "Text reference extracted: " + versionName);
viewHolder.textView.setText(versionName);

int imageIndex = cursor.getColumnIndex(MyCityContract.MyCityEntry.COLUMN_ICON);
int image = cursor.getInt(imageIndex);
Log.i(LOG_TAG, "Image reference extracted: " + image);

viewHolder.imageView.setImageResource(image);
}
}


Plese bare in mind that the logcat of this class gives me

I/MyCityAdpapter: Text reference extracted: Ancient Theatre - Larissa
I/MyCityAdpapter: Image reference extracted: 0
I/MyCityAdpapter: Text reference extracted: Old trains
I/MyCityAdpapter: Image reference extracted: 0


and so on.

MyCityContract

public class MyCityContract {

public static final String CONTENT_AUTHORITY = "theo.testing.customloaders.app";

public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

public static final class MyCityEntry implements BaseColumns{
//table name
public static final String TABLE_MY_CITY = "my_city";
//columns
public static final String _ID = "_id";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_ICON = "icon";

// create content uri
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
.appendPath(TABLE_MY_CITY).build();
// create cursor of base type directory for multiple entries
public static final String CONTENT_DIR_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + TABLE_MY_CITY;
// create cursor of base type item for single entry
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE +"/" + CONTENT_AUTHORITY + "/" + TABLE_MY_CITY;

// for building URIs on insertion
public static Uri buildFlavorsUri(long id){
return ContentUris.withAppendedId(CONTENT_URI, id);
}

}
}


Maybe there is something wrong with my MyCityDbHelperClass where I store all the data.

public class MyCityDbHelper extends SQLiteOpenHelper{
public static final String LOG_TAG = MyCityDbHelper.class.getSimpleName();
//name & version
public static final String DATABASE_NAME = "city.db";
public static final int DATABASE_VERSION = 7;
// Create the database
public MyCityDbHelper(Context context) {
super(context, DATABASE_NAME,null,DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
final String SQL_CREATE_MY_CITY_TABLE = "CREATE TABLE " +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "(" + MyCityContract.MyCityEntry._ID +
" INTEGER PRIMARY KEY AUTOINCREMENT, " +
MyCityContract.MyCityEntry.COLUMN_NAME + " TEXT NOT NULL, " +
MyCityContract.MyCityEntry.COLUMN_ICON + " INTEGER NOT NULL);";


sqLiteDatabase.execSQL(SQL_CREATE_MY_CITY_TABLE);
}

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
Log.w(LOG_TAG, "Upgrading database from version " + oldVersion + " to " +
newVersion + ". OLD DATA WILL BE DESTROYED");
// Drop the table
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + MyCityContract.MyCityEntry.TABLE_MY_CITY);
sqLiteDatabase.execSQL("DELETE FROM SQLITE_SEQUENCE WHERE NAME = '" +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "'");

// re-create database
onCreate(sqLiteDatabase);
}
}


and finally I have my provider.

public class MyCityProvider extends ContentProvider {
private static final String LOG_TAG = MyCityProvider.class.getSimpleName();
private static final UriMatcher sUriMatcher = buildUriMatcher();
private MyCityDbHelper myCityDbHelper;

//Codes for UriMatcher
private static final int MY_CITY = 100;
private static final int MY_CITY_WITH_ID = 200;


private static UriMatcher buildUriMatcher(){
// Build a UriMatcher by adding a specific code to return based on a match
// It's common to use NO_MATCH as the code for this case.
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = MyCityContract.CONTENT_AUTHORITY;

//add code for each URI
matcher.addURI(authority,MyCityContract.MyCityEntry.TABLE_MY_CITY,MY_CITY);
matcher.addURI(authority,MyCityContract.MyCityEntry.TABLE_MY_CITY + "/#",MY_CITY_WITH_ID);

return matcher;

}
@Override
public boolean onCreate() {
myCityDbHelper = new MyCityDbHelper(getContext());

return true;
}

@Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);

switch (match){
case MY_CITY: {
return MyCityContract.MyCityEntry.CONTENT_DIR_TYPE;
}
case MY_CITY_WITH_ID:{
return MyCityContract.MyCityEntry.CONTENT_ITEM_TYPE;

}
default:{
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
Cursor retCursor;
switch(sUriMatcher.match(uri)){
// All Flavors selected
case MY_CITY:{
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
return retCursor;
}
// Individual flavor based on Id selected
case MY_CITY_WITH_ID:{
retCursor = myCityDbHelper.getReadableDatabase().query(
MyCityContract.MyCityEntry.TABLE_MY_CITY,
projection,
MyCityContract.MyCityEntry._ID + " = ?",
new String[] {String.valueOf(ContentUris.parseId(uri))},
null,
null,
sortOrder);
return retCursor;
}
default:{
// By default, we assume a bad URI
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
}




@Override
public Uri insert(Uri uri, ContentValues contentValues) {
final SQLiteDatabase db = myCityDbHelper.getWritableDatabase();

Uri returnUri;

switch (sUriMatcher.match(uri)){
case MY_CITY:

long _id = db.insertWithOnConflict(MyCityContract.MyCityEntry.TABLE_MY_CITY,null,contentValues,SQLiteDatabase.CONFLICT_REPLACE);
Log.d("id",String.valueOf(_id));
// insert unless it is already contained in the database
if(_id>0){
returnUri = MyCityContract.MyCityEntry.buildFlavorsUri(_id);
}else {
throw new android.database.SQLException("Failed to insert row into: " + uri);
}
break;
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri );
}
}

getContext().getContentResolver().notifyChange(uri,null);
return returnUri;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = myCityDbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int numDeleted;
switch(match){
case MY_CITY:
numDeleted = db.delete(
MyCityContract.MyCityEntry.TABLE_MY_CITY, selection, selectionArgs);
// reset _ID
db.execSQL("DELETE FROM SQLITE_SEQUENCE WHERE NAME = '" +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "'");
break;
case MY_CITY_WITH_ID:
numDeleted = db.delete(MyCityContract.MyCityEntry.TABLE_MY_CITY,
MyCityContract.MyCityEntry._ID + " = ?",
new String[]{String.valueOf(ContentUris.parseId(uri))});
// reset _ID
db.execSQL("DELETE FROM SQLITE_SEQUENCE WHERE NAME = '" +
MyCityContract.MyCityEntry.TABLE_MY_CITY + "'");

break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}

return numDeleted;
}


@Override
public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs){
final SQLiteDatabase db = myCityDbHelper.getWritableDatabase();
int numUpdated = 0;

if (contentValues == null){
throw new IllegalArgumentException("Cannot have null content values");
}

switch(sUriMatcher.match(uri)){
case MY_CITY:{
numUpdated = db.update(MyCityContract.MyCityEntry.TABLE_MY_CITY,
contentValues,
selection,
selectionArgs);
break;
}
case MY_CITY_WITH_ID: {
numUpdated = db.update(MyCityContract.MyCityEntry.TABLE_MY_CITY,
contentValues,
MyCityContract.MyCityEntry._ID + " = ?",
new String[] {String.valueOf(ContentUris.parseId(uri))});
break;
}
default:{
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}

if (numUpdated > 0){
getContext().getContentResolver().notifyChange(uri, null);
}

return numUpdated;
}
}


So why the image is not there? I always wanted to solve the issue of storing dynamic data and read them in offline mode too:).

Thanks,

Theo.

Answer

You are storing image url in your database, but you are reading an Integer in your MyCityAdapter. You need to get image url again in bindView() method. Once you get the url of the image, you will have to download and store the image itself. But there is no need to write it all yourself. I recommend Picasso library. Once you add this library to your dependecies, you can set image to your imageView like following: Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

So, your bindView() method body will be something like this:

@Override
public void bindView(View view, Context context, Cursor cursor) {
   ViewHolder viewHolder = (ViewHolder) view.getTag();

   Log.d(LOG_TAG, "In bind View");

   int versionIndex = cursor.getColumnIndex(MyCityContract.MyCityEntry.COLUMN_NAME);
   final String versionName = cursor.getString(versionIndex);
   Log.i(LOG_TAG, "Text reference extracted: " + versionName);
   viewHolder.textView.setText(versionName);

   int imageIndex = cursor.getColumnIndex(MyCityContract.MyCityEntry.COLUMN_ICON);
   String imageUrl = cursor.getString(imageIndex);
   Log.i(LOG_TAG, "Image reference extracted: " + image);

   Picasso.with(context).load(imageUrl).into(viewHolder.imageView);
}