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?
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: