Search code examples
androidserviceandroid-contentprovider

Content Provider fault


In my app I use a content provider. As you know the content provider is the middle man between the client and SQLite. In my case I retrieve the data from a server using volley,store them in SQLite, and finally read them using the ContentResolver object and the LoaderManager interface(which has onCreateLoader,onLoadFinished,onLoaderReset). I also use a service, as I want to run my webservice, when the app is closed.

MyService

  public class MyService extends IntentService {
  private final String LOG_TAG = MyService.class.getSimpleName();


  public MyService() {
    super("My Service");
  }

  @Override
  protected void onHandleIntent(Intent intent) {
    updateCityList();
  }
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
  public void updateCityList() {

    cityList.clear();

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

    // 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) {

            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++) {

                    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"));

                    getContentResolver().insert(MyCityContract.MyCityEntry.CONTENT_URI, imageValues);
                    cityList.add(item);

                    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);
}
static public class AlarmReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent sendIntent = new Intent(context, MyService.class);
        context.startService(sendIntent);
    }
   }
}

MainActivityFragment

 public class MainActivityFragment extends Fragment implements    

 LoaderManager.LoaderCallbacks<Cursor>{
 static public ArrayList<MyCity> cityList;
 public String [] MY_CITY_PROJECTIONS = {MyCityContract.MyCityEntry._ID,
        MyCityContract.MyCityEntry.COLUMN_NAME,
        MyCityContract.MyCityEntry.COLUMN_ICON};
private static final String LOG_TAG =    
MainActivityFragment.class.getSimpleName();
public static MyCityAdpapter myCityAdpapter;
private static final int CURSOR_LOADER_ID = 0;
private GridView mGridView;

public MainActivityFragment() {
    // Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Add this line in order for this fragment to handle menu events.
    setHasOptionsMenu(true);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.refresh, menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_refresh) {

        return true;
    }
    return super.onOptionsItemSelected(item);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {

    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);
    Cursor c =
            getActivity().getContentResolver().query(MyCityContract.MyCityEntry.CONTENT_URI,
                    new String[]{MyCityContract.MyCityEntry._ID},
                    null,
                    null,
                    null);
    if (c.getCount() == 0){
        updateCityData();
    }
    // initialize loader
    getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
    super.onCreate(savedInstanceState);
    return rootView;

}

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

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


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

public void updateCityData() {

    Intent alarmIntent = new Intent(getActivity(), MyService.AlarmReceiver.class);


    //Wrap in a pending intent which only fires once.
    PendingIntent pi = PendingIntent.getBroadcast(getActivity(), 0,alarmIntent,PendingIntent.FLAG_ONE_SHOT);//getBroadcast(context, 0, i, 0);

    AlarmManager am=(AlarmManager)getActivity().getSystemService(Context.ALARM_SERVICE);

    //Set the AlarmManager to wake up the system.
    am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, pi);
    }
 }

I just setup an alarm manager to make my service run after 5 seconds. This is just for testing really. Anyway,here is my problem. When I launch the app for the first time,nothing in shown in my screen. When I exit though,and launch it again,I can see all the images in my gridview. Why is this happening? To make more clear

When I launch the app for the first time:

10-16 12:07:00.799 16685-16685/theo.testing.androidcustomloaders D/ContentValues: [{"id":"15","title":"The Gate of Larissa","image":"larissa17.png"},{"id":"14","title":"Larissa Fair","image":"larissa14.png"},{"id":"13","title":"Larissa Fair","image":"larissa13.png"},{"id":"12","title":"AEL FC Arena","image":"larissa12.png"},{"id":"11","title":"AEL FC Arena","image":"larissa11.png"},{"id":"10","title":"Alcazar Park","image":"larissa10.png"},{"id":"9","title":"Alcazar Park","image":"larissa9.png"},{"id":"8","title":"Church","image":"larissa8.png"},{"id":"7","title":"Church","image":"larissa7.png"},{"id":"6","title":"Old trains","image":"larissa6.png"},{"id":"5","title":"Old trains","image":"larissa5.png"},{"id":"4","title":"Munipality Park","image":"larissa4.png"},{"id":"3","title":"Munipality Park","image":"larissa3.png"},{"id":"2","title":"Ancient Theatre - Larissa","image":"larissa2.png"},{"id":"1","title":"Ancient Theatre - Larissa","image":"larissa1.png"}]

In order to display the data I need to exit the app and launch it again. Why is this happening? Is there something wrong with my code?


Solution

  • LoadManager doesn't handle Your changes in database because it doesn't have any connection to it. You must register observer to handle that stuff.

    In Your myCityProvider, in query(...) method is missing method setNotificationUri. It should be set at the end.

    Here is modified Your query method:

    @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);
                    break;
                }
                // 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);
                    break;
                }
    
                default: {
                    // By default, we assume a bad URI
                    throw new UnsupportedOperationException("Unknown uri: " + uri);
                }
            }
    
            if (retCursor != null) {
                retCursor.setNotificationUri(getContext().getContentResolver(), uri);
            }
    
            return retCursor;
        }
    

    I've checked Your git repo and I think You should fix Your MainActivityFragment. You do everyting in onCreateView but You should do here all stuff related to view or just return inflated view. And after that, You can do the rest in onViewCreated.

    You should do In this way:

    @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_main_activity, container, false);
        }
    
        @Override
        public void onViewCreated(View rootView, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(rootView, savedInstanceState);
    
            myCityAdpapter = new MyCityAdpapter(getActivity(), null, 0);
            mGridView = (GridView) rootView.findViewById(R.id.flavors_grid);
            mGridView.setAdapter(myCityAdpapter);
    
            getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
        }
    
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    
            switch (id) {
                case CURSOR_LOADER_ID:
                    return new CursorLoader(getActivity(),
                            MyCityContract.MyCityEntry.CONTENT_URI,
                            null,
                            null,
                            null,
                            null);
                default:
                    throw new IllegalArgumentException("id not handled!");
            }
        }
    
        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            switch (loader.getId()) {
                case CURSOR_LOADER_ID:
                    if (data == null || data.getCount() == 0) {
                        updateCityData();
                    } else {
                        myCityAdpapter.swapCursor(data);
                    }
                    break;
            }
        }
    
        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            myCityAdpapter.swapCursor(null);
        }
    

    Thanks to that:

    • if app opens, loader will load all what he have (can be 0 items) but if there aren't any items it will call service to download more and store in db
    • if you add any data by service, onLoadFinished will be called again and refresh adapter