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.
 
     
    