Why would my fragment crash when reloading the loader? - android

While I have been unable to duplicate this bug, I still get a trickle of crash reports with this occurring. I thought adding a null check for my list adapter would fix it, but its still occurring. What am I missing?
Full stacktrace:
http://pastebin.com/Q6GwDU7Q
public void onLoaderReset(Loader<Cursor> loader) {
final int id = loader.getId();
switch (id) {
case LOADER_ID1:
if (mAdapter != null)
mAdapter.changeCursor(null); //Line 512 where stacktrace references
break;
case LOADER_ID2:
//Other code here
break;
default:
throw new InvalidParameterException("id=" + id);
}
}
mAdapter is initialized in onActivityCreated, but I realize while typing this I do not ever release it, maybe I should perform that in onDetach? mAdapter is attached to a ListView set up by a ListFragment. And I set the adapters cursor to null to clear the list I have. So yes, what am I overlooking?

Well after searching through the Android source, I see now that like GreyBearedGeek stated, I should allow the loader to handle the Cursor destruction.
As you can see in CursorLoader it will handle closing the old cursor if passing a new one:
/* Runs on the UI thread */
#Override
public void deliverResult(Cursor cursor) {
if (isReset()) {
// An async query came in while the loader is stopped
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;
if (isStarted()) {
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
As well as closing the cursor upon a reset.
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
So I will use swapCursor in the future as to not interfere with CursorLoaders handling, even though I've yet to see how this causes my NPE in the stacktrace above.

Related

Extracting data from Cursor in onLoadFinished for Loader Manager

I have the lines of code in onLoadFinished for my Loader Manager callback in a fragment. The problem with the code is that the favoriteMovies is needed to be displayed in the ArrayAdapter so fo what ever reason, it is zero while the log statement show that the cursor or data is not empty.
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.i(TAG, "Loader Manager Loading Finished: " + data.getCount());
if (data != null && data.getCount() > 0) {
while (data.moveToNext()) {
favouriteMovies.add(
new Movie(
data.getString(2),
data.getString(3),
data.getString(4),
data.getInt(1),
data.getString(5),
data.getDouble(6),
data.getDouble(7)
));
}
setMovies(favouriteMovies);
hideLoadingProgress();
mAdapter.notifyDataSetChanged();
} else {
showEmptyView(EMPTY_VIEW_TYPE_LOCAL);
hideLoadingProgress();
}
}
How can i write this code so that it actually waits fro the while statement to finish before calling the setMovies() method. When i minimize my app to the background and restore it, the movies are diaplayed, but it does not work in the first time it is called.
After much trial i simple have to check the cursor to know if it is the last index and then call my other function as shown below.
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.i(TAG, "Loader Manager Loading Finished: " + data.getCount());
favouriteMovies.clear();
if (data != null && data.getCount() > 0) {
while (data.moveToNext()) {
favouriteMovies.add(
new Movie(
data.getString(2),
data.getString(3),
data.getString(4),
data.getInt(1),
data.getString(5),
data.getDouble(6),
data.getDouble(7)
));
if (data.isLast()) {
setMovies(favouriteMovies);
hideLoadingProgress();
mAdapter.notifyDataSetChanged();
}
}
} else {
showEmptyView(EMPTY_VIEW_TYPE_LOCAL);
hideLoadingProgress();
}
}

ArrayAdapter.clear() invokes on a null object reference

I have the following code in an AsyncTask that retrieves a list of notifications from a DB.
protected void onPostExecute(List<NotificationItem> result) {
super.onPostExecute(result);
if (dialog.isShowing())
dialog.dismiss();
if (adpt != null) {
if (result != null && result.size() > 0)
adpt.setItemList(result);
else if (adpt.getItemList() != null)
adpt.clear();
adpt.notifyDataSetChanged();
}
srl.setRefreshing(false);
}
As you can see, I check the result, the adapter (adpt) and its items (adpt.getItemList()) for being null, before making the relevant action such as .clear(). I want to clear the list when the result size is 0 meaning nothing should be shown to the user (before, it did). When the debug is on the .clear() line, all three objects are not null but still I get NullPointerException.
When debugging inside clear(), I got inside ArrayAdapter.java:
public void clear() {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.clear();
} else {
mObjects.clear();
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
And both mOriginalValues and mObjects are null, hence the error. Not sure what am I doing wrong, can you see the problem?
Update: I instantiate the adapter like this: notificationsAdapter = new NotificationsAdapter(null); ofcourse I do this as first step when I still don't have any items.
The error is:
java.lang.NullPointerException: Attempt to invoke interface method
'void java.util.List.clear()' on a null object reference at
android.widget.ArrayAdapter.clear(ArrayAdapter.java:258) at
com.example.example.fragments.NotificationsList$NotificationsRetriever.onPostExecute(NotificationsList.java:184)

Using popBackStack() in Android does not update android-listview with Firebase data

At the beginning of the chat app user see a list off groups (listview group) available and the user have the possibility to create a new group or click on some off the available groups and then start to write messages (listview messages). The functions CreateNewMessage and CreateNewGroup pushes information to firebase correctly
Above scenarios works finne problems arise when user navigates backwards (popBackStack()) from listview with messages to GroupFragment, here should user be presented a list off available groups but the listview is empty. The ReadGroupData() function is not reading the already created groups from firebase and inserts them in the group listview. How to make this happen?
GroupFragment:
public void ReadGroupData() {
Firebase firebaserootRef = new Firebase("https://000.firebaseio.com");
firebaserootRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot snapshot, String s) {
if (snapshot.getValue() != null) {
Group newGroup = new Group((String)snapshot.child("name").getValue(),
(String) snapshot.child("id").getValue());
if(!groupKeyValues.contains(newGroup.GetId())) {
groupKeyValues.add(newGroup.GetId());
AddToLstViewGroup(newGroup);
System.out.println("Read group data from firebase and
inserted in listView");
}
}
}
});
}
public void AddToLstViewGroup(Group newGroup) {
groupNameList.add(newGroup);
if(groupAdapter == null) {
groupAdapter = new GroupAdapter(getActivity(), groupNameList);
}
if (lstViewGroup == null) {
lstViewGroup = (ListView) getView().
findViewById(R.id.listView_group);
}
lstViewGroup.setOnItemClickListener(onItemClickListener);
lstViewGroup.setOnItemLongClickListener(onItemLongClickListener);
groupAdapter.notifyDataSetChanged();
lstViewGroup.setAdapter(groupAdapter);
}
ChatFragment:
public void ReadChatMessages(Firebase firebaseRootRef) {
firebaseRootRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot snapshot, String s) {
if (snapshot.child(GetGroupId()).child("messages").
getChildren() != null) {
for (DataSnapshot c :
snapshot.child(GetGroupId()).child("messages").getChildren()) {
String key = c.getKey();
Message newMessage = new Message();
newMessage.SetFrom((String) c.child("from").getValue());
newMessage.SetMsg((String)
c.child("message").getValue());
newMessage.SetTime((String) c.child("time").getValue());
newMessage.SetId((String) c.child("id").getValue());
if ((!msgKeyValues.contains(key)) ||
newMessage.GetFrom() != "") {
msgKeyValues.add(key);
AddToLstViewChat(newMessage);
//Automatic scrolls to last line in listView.
lstViewChat.setSelection(chatAdapter.getCount() -1);
}
}
}
}
public void AddToLstViewChat(Message newMessage) {
chatMsgList.add(newMessage);
if (chatAdapter == null) {
chatAdapter = new ChatAdapter(getActivity(), chatMsgList);
}
if(IsMsgFromMe(newMessage)) {
lstViewChat = (ListView)
getView().findViewById(R.id.listView_chat_message_me);
} else {
lstViewChat =
(ListView)getView().findViewById(R.id.listView_chat_message_others);
}
chatAdapter.notifyDataSetChanged();
lstViewChat.setAdapter(chatAdapter);
}
ChatActivity:
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
finish();
}
}
For all the code click on the link: "http://pastebin.com/97nR68Rm"
SOLUTION!
Kato thank you for you patience and help. I have now found a solution for the problem. I'm calling ReadGroupData() and ReadChatMessages() at the end (before return) in my onCreateView methods. As Kato pointed out onCreate() is not getting called on popBackStack()
In my AddToLStViewGroup the if statement for lstViewGroup is deleted so now it always sets the listView otherwise it will throw an exception for not finding the correct view, To clarifying:
Deleted this line:
if (lstViewGroup == null) {
lstViewGroup = (ListView)getView().findViewById(R.id.listView_group);
}
And replaced with:
ListView lstViewGroup=(ListView)getView().findViewById(R.id.listView_group);
Kato thank you for you patience and help. I have now found a solution for the problem. I'm calling ReadGroupData() and ReadChatMessages() at the end (before return) in my onCreateView methods. As Kato pointed out onCreate() is not getting called on popBackStack()
In my AddToLStViewGroup the if statement for listViewGroup is deleted so now it always sets the listView otherwise it will throw an exception for not finding the correct view.
To clarify:
I deleted this line:
if (lstViewGroup == null) {
lstViewGroup = (ListView)getView().findViewById(R.id.listView_group);
}
And replaced it with:
ListView lstViewGroup =(ListView)getView().findViewById(R.id.listView_group);
(The original asker posted the answer as part of the question. I'm copying it here as a matter of housekeeping.)

Usage CursorLoader without ContentProvider and avoiding database leaks

I have implemented the class found in this question:
CursorLoader usage without ContentProvider
It is a means of using the LoaderManager and CursorLoader without a content resolver. I am using it to load data from a SQLite database and display it in a ListFragment.
The problem I am seeing is that the database is leaking. Obviously this is because I am not closing the database when I am done.
I have now started to do this, but I am concerned as the database could be accessed at any time by background tasks scheduled with the AlarmManager. I am worried that I might close the database when another class needs it open.
My solution has been to count the opens/closes and only close the database when no one is using it. Like so:
public synchronized SQLiteDatabase openDataBase()
{
try
{
mDatabaseUsers++;
Log.d(TAG, "DatabaseUsers: " + mDatabaseUsers);
// If already open, return it.
if (mOpenDatabase != null && mOpenDatabase.isOpen())
return mOpenDatabase;
OpenHelper openHelper = new OpenHelper(mContext);
return openHelper.getWritableDatabase();
} catch (SQLException e)
{
Log.e("MessageDelay", "Error opening database: " + e.toString());
return null;
}
}
public synchronized void closeDatabase()
{
mDatabaseUsers--;
// If no one is using the database, close it.
if (mOpenDatabase != null && mDatabaseUsers == 0)
{
mOpenDatabase.close();
}
Log.d(TAG, "DatabaseUsers: " + mDatabaseUsers);
}
This appears to work, but it has meant adding an extra line of code all over my application. Furthermore I've had trouble with the LoaderManager not behaving as expected and it calls its reset function more than it does its load, so I've had to put this fix in:
return new SimpleCursorLoader(getActivity())
{
private int mDBOpens = 0;
#Override
public Cursor loadInBackground()
{
mDBOpens++;
return JSQLite.getSingleton(getActivity()).retrieveTextsSent(mMode == 1 ? true : false);
}
#Override
public void reset()
{
if (mDBOpens > 0)
{
JSQLite.getSingleton(getContext()).closeDatabase();
}
super.reset();
mDBOpens--;
}
};
It feels like this isn't the correct way of doing it. Is there another, cleaner means of closing/opening the database only when needed?
Thanks, Jason.

Responding to updates on SQLiteDatabase

I have an ImageButton that I want to use to set a boolean value for an item in an SQLiteDatabase.
The ImageButton will display one image for a value of "1", and another image for a value of "0". Pressing the image button should toggle the database field and therefore its image.
For some reason, when I press the button the value returned by the currentCursor.getInt() is still the same, despite the update method being called on the database. Do I have to update/refresh the cursor?
In my activity I have:
private void updateFavouriteButton(){
int favourite = currentCursor.getInt(currentCursor.getColumnIndex(Object.favourite));
if (favourite == 1)
{
favouriteButton.setImageDrawable(getResources().getDrawable(R.drawable.favourite_selected));
favouriteButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
dbHelper.updateFavourite(selectedId, 0);
updateFavouriteButton();
}
});
}
else
{
favouriteButton.setImageDrawable(getResources().getDrawable(R.drawable.favourite));
favouriteButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
dbHelper.updateFavourite(selectedId, 1);
updateFavouriteButton();
}
});
}
}
In my database helper I have:
public void updateFavourite(long rowId, int favourite)
{
db.beginTransaction();
try {
ContentValues args = new ContentValues();
args.put("ZFAVOURITE", favourite);
int rowsAffected = db.update("ZOBJECT", args,"_id" + "=" + rowId, null);
if (rowsAffected > 0)
db.setTransactionSuccessful();
} catch (Exception e) {
Log.e("Error in transaction", e.toString());
} finally {
db.endTransaction();
}
}
requery() is deprecated, the documentation says to just close the old cursor, remake it, and use the new one.
A cursor is a snapshot at the time its created. If the Database is changed, you must requery.
As #Pyrodante had said, it is needed to requery() the cursor in some ways since requery() is deprecated
Not sure how u guys solve it, here is how I solve this problem.
please let me know if there is a better way. thanks!
Cursor newCursor=CreateCursor(); //ChreateCursor() create a method which returns you a new cursor
mAdapter.changeCursor(newCursor); //Change to the new cursor so that the list will be updated

Categories

Resources