Activity lifecycle vs View lifecycle: how to avoid NPE? - android

I have a ListView which adapts data from a cursor. The cursor's position of the record is stored via View.setTag(), so it can be retrieved in response to user events.
public class OrderActivity extends Activity {
private ListView list;
private CursorAdapter adapter;
private SQLiteDatabase database;
public void onResume() {
database = new Database(this).getWriteableDatabase();
// query the db and store the position of the cursor
// as the tag while binding the view in the overridden
// CursorAdapter.bindView()
adapter = new SimpleCursorAdapter( /* code here */ );
list.setAdapter(adapter);
}
public void onPause() {
adapter.changeCursor(null);
database.close();
}
private void onClickRowDumpOrder(View row) {
int newPosition = (Integer) row.getTag();
Cursor cursor = adapter.getCursor();
int originalPosition = cursor.getPosition(); // Throws NPE
cursor.moveToPosition(newPosition);
Log.v("tag", "Description: " + cursor.getString(0));
// restore the cursor
cursor.moveToPosition(originalPosition);
}
}
I store the database and the adapter in instance fields of my Activity to allocate/free resources during the activity lifecycle: I create a new database onResume and close it onPause. Unfortunately, I receive lots of reports from my users that a NPE is thrown in the line outlined in the pseudocode above, but I'm not able to reproduce it.
It seems that cursor is null, but I wonder how this is possible if the method onClickRowDumpOrder can only be called after onResume, since it's the callback to the click event (I set this in the XML layout via android:onClick)
Am I doing something wrong? What API misusage causes the cursor to be null? Is there some documentation describing how cursors and adapters are intended to fit in the activity lifecycle?
UPDATE
I got rid of android:onClick in my XML file and manually set the listener inside SimpleCursorAdapter.bindView. To avoid leaks, I remove the listener either in my custom AbsListView.RecycleListener and in my activity's onPause (I retrieve all views with reclaimViews(List<View>)). This seems to fix the bug, and here is my explanation.
A new View is inflated when my activity first starts
instance#1 of my activity is set as the OnClickListener for that particular View in the View's constructor, when it parses the android:onClick attribute
instance#1 leaves the foreground, thus onPause() sets the cursor to null. Note that this activity is still the listener for the View, because neither the view nor the activity are marked for garbage collection. This means that they are referenced in some cache in the Android classes
instance#2 of my activity is created, and its list view somewhat recycle the already created View. The data is shown correctly, but this view still has the old activity (with a null cursor) as the listener
when user clicks my view, instance#1 handler is called, but it has a null cursor. This causes the NPE
This explanation is realistic, however I didn't find relevant code in Android classes (there is a cache in AbsListView.RecycleBin, but the ListView itself is not reused). Moreover, I've never been able to reproduce this bug, and I solely think my fix works because in the last two days I received no reports (usually I get a few dozens a day)
Are you aware of any code in the Android stack which can validate my assumptions?

you can try this way.
if (cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
}
}

I think you are creating the cursor at the wrong point in the Activity lifecycle. The ListView sample puts in onCreate() what you have in onResume(). I don't know that it's necessarily harmful, but you may be recreating some things that don't need to be.

Related

ListFragment doesn't refresh

INTRODUCTION
I've got an app which consists on an Acitivity where I create some elements and then I save this elements on a ListFragment. This stuff works fine, but the problem comes when I try to delete an element from the list.
Briefly, the listFragment doesn't refresh when I delete an element. If I go back to main Activity and then I enter again to the ListFragment, then the element that I deleted doesn't appear, but the thing would be to refresh this list at the moment I delete an element.
Have to say that I'm a bit confused because at first, it was doing this right, but I don't know what I have touched that now does not do it.
CODE
This are relevant code snippets of ListFragment:
public class MyPlacesListFragment extends ListFragment {
//...
final class PlacesCursorAdapter extends ResourceCursorAdapter {
//...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getAdapter();
}
public void getAdapter() {
Cursor c = getActivity().getContentResolver().query(PlacesProvider.CONTENT_URI, PROJECTION, null, null, null);
mCursorAdapter = new PlacesCursorAdapter(getActivity(), c);
setListAdapter(mCursorAdapter);
}
private void deleteItem(long id){
Uri uri = ContentUris.withAppendedId(PlacesProvider.CONTENT_URI, id);
getActivity().getContentResolver().delete(uri, null, null);
}
I have to say that I work with dataBase and ContentProvider, but these work fine, I've tested them with other apps.
Also, I call notifyChange() on the Insert, Update, and Delete methods of the Provider this way:
getContext().getContentResolver().notifyChange(uri, null);
Just call this:
mCursorAdapter.notifyDataSetChanged();
After you made changes to your underlying data.
Also, I'd suggest renaming your getAdapter() method, as it has a misleading name IMHO. I'd expect it to return always the same adapter, while you seem to use it to initialize a new adapter.

Adapter not working

In my OncreateView() set the adapter which is working when i am first loading the page. When i go to another page and make changes then come back to this fragment it is not working adapter.notifyDatasetchanged().
#Override
public void onStart() {
super.onStart();
groupItem.clear();
childItem.clear();
List<String> child_Category;child_Category=new ArrayList<String>();
groupItem = obj_Listdatabase.fetchcategory();
childItem.clear();
ListIterator<String> iterator = groupItem
.listIterator();
while (iterator.hasNext()) {
String categoryname = iterator.next();
child_Category = new ArrayList<String>();
child_Category = obj_Listdatabase
.fetchchildlist(categoryname);
childItem.add(child_Category);
}
adapter.notifyDataSetChanged();
}
Hai I got output for this one:
groupItem.addAll(obj_Listdatabase.fetchcategory());
Instead of This
//groupItem = obj_Listdatabase.fetchcategory();
Because i change the reference in assignment statement(=) for that adapter.So i use addAll() method to store the values only instead of reference.
You should reaaaally indent your code more cleanly, never use several ";" on the same line for example.
And you can definitely use BaseExpandableListAdapter the issu is somewhere else..
You should also notice that onCreate view is more suitable for "creating view" so all the adapter stuff should be somewhere else like onViewCreated, or onActivityCreated as you need (it's just some advices)
I assume your adapter is in a Fragment since you use GetActivity() and in the constructor of your adapter you pass a reference to the activity (context), the group list and the child list.. ok
we ll assume that you are using the fragment onStart. From the official doc :
Called when the Fragment is visible to the user. This is generally tied to Activity.onStart of the containing Activity's lifecycle.
So normally this method is called after onViewCreate, onViewCreated .. etc at least the first time you code is running. So this is ok
Did you try using adapter.notifyDataSetInvalidate() and then do adapter.notidyDataSetChanged ?
One last thing, since you actually pass the data to your adapter by the constructor, when you update your lists how can the adapter be aware of the changes ? Are your lists global (static) ?
If not, before doing adapter.notifyDataSetChanged() you should pass the lists (group and child) to the adapter with some setter..
good luck

Obtaining the current Android view and forcing it to be redrawn

How can I get the current Android view when it displays data that has been updated, and force it to be redrawn? I worked through Android's Notepad tutorial and completed lesson three without any problems — the solution is provided, after all — but I'm stuck on my first non-trivial modification.
I added a new button to the menu, next to the Add note button. When pressed, that button adds a letter to the title of each note in the system. However, the new titles don't show up in the list of notes no matter how long I wait. I know the updater works because the changes do appear if I dismiss the app and bring it back up.
So far, I've discovered that I have to use some kind of invalidation method to make the program redraw itself with the new values. I know that invalidate() is used from the UI thread and postInvalidate() is used from non-UI threads 1, 2, but I don't even know which thread I'm in. Also, both of those methods have to be called from the View object that needs drawing, and I don't know how to obtain that object. Everything I try returns null.
My main class:
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch(item.getItemId()) {
case INSERT_ID:
createNote();
return true;
case NEW_BUTTON:
expandTitles();
return true;
default:
// Intentionally empty
}
return super.onMenuItemSelected(featureId, item);
}
private void expandTitles() {
View noteListView = null;
// noteListView = findViewById(R.layout.notes_list); // null
// noteListView =
// getWindow().getDecorView().findViewById(android.R.id.content);
// From SO question 4486034
noteListView = findViewById(R.id.body); // Fails
mDbHelper.expandNoteTitles(noteListView);
}
My DAO class:
public void expandNoteTitles(View noteListView) {
Cursor notes = fetchAllNotes();
for(int i = 1; i <= notes.getCount(); i++) {
expandNoteTitle(i);
}
// NPE here when attempt to redraw is not commented out
noteListView.invalidate(); // Analogous to AWT's repaint(). Not working.
// noteListView.postInvalidate(); // Like repaint(). Not working.
}
public void expandNoteTitle(int i) {
Cursor note = fetchNote(i);
long rowId =
note.getLong(note.getColumnIndexOrThrow(NotesDbAdapter.KEY_ROWID));
String title =
note.getString(note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)) + "W";
String body =
note.getString(note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY));
updateNote(rowId, title, body);
}
What do I have to do to get the updated note titles to show up as soon as I press the button?
Obviously, I'm a complete newbie to Android. I point this out to encourage you to use small words and explain even obvious things. I know this is the millionth "Android not redrawing" question, but I've read dozens of existing posts and they either don't apply or don't make sense to me.
1: What does postInvalidate() do?
2: What is the difference between Android's invalidate() and postInvalidate() methods?
According to the tutorial, the list of existing notes are presented in a ListView. That is an adapter based View, so the items it shows are sourced from an adapter extending theBaseAdapter class. In these cases, you should notify the adapter that the contents have changed by calling its notifyDatasetChanged method. This'll signal the ListView to update and redraw its rows.
Edit:
Sorry, I now realize that this example uses CursorAdapters. These source the items to show from a Cursor object that was obtained from a database query. Now, what the notifyDatasetChanged() tells the adapter is, that the data that backs the adapter has changed, so Views that show stuff based on this adapter need to redraw their contents. In the case of a CursorAdapter, this data is coming from a cursor. So you also need to requery that cursor, refreshing it from the DB, like this:
private void expandTitles() {
mDbHelper.expandNoteTitles();
CursorAdapter adapter = (CursorAdapter)getListAdapter();
adapter.getCursor().requery();
}
The requery() method automatically calls the notifyDatasetChanged() in this case, so you don't need to worry about that, the list will update itself. See this thread also: https://groups.google.com/forum/?fromgroups#!topic/android-developers/_FrDcy0KC-w%5B1-25%5D.

Android ArrayAdapter not clearing?

I'm having a strange issue with a custom implementation of Android's ArrayAdapter. To give some background, I'm trying update a ListView's contents while preserving the current scroll position.
I have a service which executes a thread to update data that's displayed in the ListView. That data is stored in an ArrayList and that ArrayList is used to generate some custom ArrayAdapters for the ListView. The adapters are also updated when an item in the ListView is pressed (either adding or removing an item). I used to just create new adapters each time there was any type of change and then set this new adapter to the ListView. This worked, but caused the ListView to scroll to the top each time. Given the nature of my application this was undesirable. The current scrolled position in the ListView must be maintained between updates.
Instead of creating new adapters I began clearing the adapter that I needed to update using the adapter's clear() method, then rebuild the adapter's items by using the adapter's add() method. Both of these methods are being called on the adapter. The adapters are all set to notifyDataOnChange in their constructors so I don't have to manually call notiftyDatasetChanged() each time (although given my issue I've tried calling it manually as well to no avail).
Here's what my custom adapter looks like:
public class RealmAdapter extends ArrayAdapter<Realm>
{
Context c;
public RealmAdapter(Context context, int resource, int textViewResourceId)
{
super(context, resource, textViewResourceId);
setNotifyOnChange(true);
c = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
...
}
...
}
Long story short, here's my issue. When I call clear() on the adapter, the adapter is not being cleared.
Here's a snippet from my onPostExecute in my thread that does updating. I'm being sure to put it here so it's updating on the UI thread. I also have this exact code copied in a private method in my UI activity. This code does not work in either place:
appState.favoriteAdapter.clear();
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
for(Realm r : appState.favorites) {
appState.favoriteAdapter.add(r);
}
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
As an example, if the above adapter had 3 items in it, calling a getCount() right after the clear() is returning 3 instead of 0. Likewise, if the appState.favorites ArrayList only has 2 items in it, the getCount() after the loop is still returning 3, not 2. Because the adapter is not responding to any of these calls it makes it impossible to update in any fashion. I can post a Logcat later if that will be helpful, but there are no exceptions or anything useful being displayed.
After busting my head for hours, the issue I appear to be having is that the adapter is not responding to calls to any methods that alter it. I've tried passing an empty ArrayList into the adapter's super() call, this does not help. Am I missing something or using the ArrayAdapter incorrectly? I've searched all over and I've already checked a lot of the common problems such as modifying the underlying array and expecting it to update, not calling (or in my casing setting to the adapter) notifyDatasetChanged(), and using an unsupported operation on the underlying collection.
The declaration of the favoriteAdapter is very simple and is contained in my Application class:
public RealmAdapter favoriteAdapter;
Here is the initialization of the favoriteAdapter from above:
if(appState.favoriteAdapter == null) {
appState.favoriteAdapter = new RealmAdapter(c, R.layout.list_item, R.layout.realm_entry, appState.favorites);
}
else {
appState.favoriteAdapter.clear();
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
for(Realm r : appState.favorites) {
appState.favoriteAdapter.add(r);
}
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
}
The above code is in both my UI thread and the thread that downloads the refreshed data.
Underneath the code above a filter is put in place:
if(appState.favoriteAdapter != null && RealmSelector.realmFilter != null) appState.favoriteAdapter.getFilter().filter(RealmSelector.realmFilter.getText().toString());
Would the filter affect clearing the list? Logic would dictate not...
I had filters being applied to the custom ArrayAdapter. Apparently this interferes with adding and removing items from the adapter itself? I added this code to my method and it is now working:
if(appState.favoriteAdapter != null && realmFilter != null) {
appState.favoriteAdapter.getFilter().filter(realmFilter.getText().toString());
}
I'd love if anyone could explain why this matters. I thought filters were meant to select subsets of items in the adapter. In my testing I was leaving the text box that is used for the filter empty, thus no actual filter text should have been applied. Again, if someone knows what's going on and could explain to me why this fixes the problem I'd love to know.

Related Spinners

This application should have four or more related spinners which should reload when their 'parent' spinner' selection changes - as an example with 2 spinners: houses, and rooms - if you choose a house, the room spinner should reload from the sqlite database.
I have tried two approaches: a MySpinner class that takes a "child" Spinner in its constructor and tells the child to update itself when OnSelectedItem is triggered, like so
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (MySpinner.this.mChild.equals(null) == false) {
MySpinner.this.mChild.updateData((int)id);
}
}
the child's updateData is
public void updateData (int parentValue) {
new backgroundTask().execute("create");
}
which background tasks is an AsyncTask to query the sqlite database:
#Override
protected Void doInBackground(String... params) {
Db = new MyDatabase(mContext);
Db.open();
if(params[0] == "create") {
if (mTable.equals("T_room")){
mCursor = mDb.getRooms(mParentValue);
}
}
return null;
}
My second approach has been to create all my spinners directly in the activity.java file. This second approach has me implement one AsyncTask for all 4 or more spinners and choose what to query from the db, based on who calls with what value.
The first approach crashes on the only 'real' line of code in the asynctask, the second approach drives me mad with autosetting spinners and a jumble of ifs in the asynctask.
I'm not a coder by any means, and wonder if someone well versed in object-oriented coding can enlighten me as to what would be good coding behaviour to solve my specific problem (several spinners that update each other on selection.)
This is interesting, at this moment I'm doing something similar. Just keep a reference to the adapter, and inside onItemSelected access the object with adapter.getItem(pos). Then you can use this object to update the second spinner adapter. Just take care of UI threading. I would like to do this in a cleaner way but I don't know how to do it.

Categories

Resources