5

EDIT: I didn't post my XML for this dialog.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tag_layout"
    android:orientation="vertical"
    android:layout_height="wrap_content"
    android:layout_width="@dimen/min_dialog_width"
    android:padding="5dp"
    android:animateLayoutChanges="true"
    >

<!-- Here is the view to show if the list is emtpy -->
<TextView
        android:id="@android:id/empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="50dp"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:text="@string/no_items"
        android:visibility="invisible"
        />

<ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="invisible"
        />

<ProgressBar
        android:id="@+id/tag_spin_progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:indeterminate="true"
        />

</RelativeLayout>

I am using the android.support.v4.CursorLoader and CursorAdapter and I am trying to get it to update its cursor. In Android 2.3.3 it works just fine. However when I try it on my 4.0.3 device the ListView doesn't refresh and the newView method in my adapter is never called. I know the cursor has data in it since I can see it on my 2.3.3 device.

If I rotate my device the ListView shows what I want. I have tried invalidating the ListView but that doesn't solve the issue.

If I don't reset the ListView's adapter the list doesn't go blank, but it still doesn't refresh the list.

I am doing all of this inside of an extended AlertDialog that is embedded in a DialogFragment.

Here is the entire class

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;

import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;

import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import org.lds.ldssa.service.MLDatabase;
import org.lds.ldssa.service.aws.Annotation;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class TagDialog extends AlertDialog implements LoaderManager.LoaderCallbacks<Cursor>,AdapterView.OnItemClickListener {

private static final String TAG = "ldssa.tagdialog";
public static final int TAGLOADERID = 0;

// View Items
private EditText mEditText;
private ListView mListView;
private TextView mEmptyView;
private ProgressBar mProgressBar;
private ImageButton mNewTagButton;
private ImageButton mSortTagButton;
private TextView mTitle;
private String mTagTitle;
private String mNewTagTitle;

private Annotation mAnnotation;
private ContentFragment mContentFragment;

private boolean isNewTagView;
private static final String KEY_NEWTAGVIEW = "new_tag_view";

private static final String POSITION_KEY = "TAG_POSITION";
private static final String Y_KEY = "TAG_Y";
private static final String SORT_KEY = "TAG_SORT";
private static final String CHECKED_STATE_KEY = "TAG_CHECKED_STATE";
private static final int NOT_SET = -1;
private int mPosition;
private int mY;

private TagSuggestionAdapter mSuggestionAdapter;
private TagListAdapter mTagAdapter;

private MLDatabase mlDatabase;
private boolean mSortAlpha;
private HashMap<Long, CheckedState> mCheckedState;

protected TagDialog(Context context) {
    super(context);
}

public void onCreate(Bundle savedInstanceState){
    Context context = getContext();
    Resources r = context.getResources();

    final LayoutInflater inflater = LayoutInflater.from(context);
    View view = inflater.inflate(R.layout.dialog_tag, null);

    // Main parts of the view        
    mEditText = (EditText) view.findViewById(R.id.tag_new_tag);
    mListView = (ListView) view.findViewById(android.R.id.list);
    mProgressBar = (ProgressBar) view.findViewById(R.id.tag_spin_progress_bar);
    mEmptyView = (TextView) view.findViewById(android.R.id.empty);
    mEmptyView.setVisibility(View.INVISIBLE);

    // Titlebar
    View titleBar = inflater.inflate(R.layout.dialog_tag_title, null);
    mNewTagButton = (ImageButton) titleBar.findViewById(R.id.tag_new_icon);
    mSortTagButton = (ImageButton) titleBar.findViewById(R.id.tag_sort_icon);
    mTitle = (TextView) titleBar.findViewById(R.id.tag_title);
    mTagTitle = r.getString(R.string.tag_dialog_title);
    mNewTagTitle = r.getString(R.string.tag_new_dialog_title);
    this.setCustomTitle(titleBar);

    // Buttons
    final String OK = r.getString(R.string.ok);
    final String CANCEL = r.getString(R.string.cancel);
    this.setButton(BUTTON_POSITIVE, OK, new OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) { /*Never Used*/}});
    this.setButton(BUTTON_NEGATIVE, CANCEL, new OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) { /*Never Used*/}});

    // Setup Button Listeners
    setOnShowListener(new OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            Button ok = getButton(BUTTON_POSITIVE);
            ok.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(isNewTagView){
                        hideIMM();
                        addNewTag();
                        mEditText.setText("");
                        setupTagDialog();
                    } else {
                        Collection<CheckedState> changes = mCheckedState.values();
                        boolean success = true;
                        MLDatabase db = getDatabase();
                        db.beginAnnotationTransaction();
                        for(CheckedState change : changes){
                            if(!change.checked()){
                                //Detag
                                db.detagAnnotation(mAnnotation.getDbKey(), change.tagID());
                            } else {
                                mAnnotation.saveHighlightsToDatabase(db);
                                if(mAnnotation.getDbKey().intValue() != MLDatabase.NOT_SET_INT &
                                        change.tagID() != MLDatabase.NOT_SET_INT){
                                    success = db.tagAnnotation(mAnnotation.getDbKey(), change.tagID(), change.changed());
                                }
                            }
                        }
                        if(success){
                            db.setAnnotationTransactionSuccessful();
                        }
                        db.endAnnotationTransaction();
                        mCheckedState.clear();
                        dismiss();
                    }
                }
            });

            Button cancel = getButton(BUTTON_NEGATIVE);
            cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(isNewTagView){
                        hideIMM();
                        setupTagDialog();
                        mEditText.setText("");
                    } else {
                        mCheckedState.clear();
                        dismiss();
                    }
                }
            });
        }
    });

    mNewTagButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            setupNewTagDialog();
        }
    });
    mSortTagButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mSortAlpha = !mSortAlpha;
            restartLoader();
        }
    });

    mListView.setOnItemClickListener(TagDialog.this);

    mEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {}

        @Override
        public void afterTextChanged(Editable s) {
           LoaderManager lm = getLoaderManager();
            if(lm != null){
                Loader l = lm.getLoader(TAGLOADERID);
                if(l != null){
                    l.forceLoad();
                } else {
                    restartLoader();
                }
            } else {
                restartLoader();
            }
        }
    });

    //Handle Rotations
    if(savedInstanceState == null){
        //New
        mPosition = NOT_SET;
        mY = NOT_SET;
        mSortAlpha = false;
        mCheckedState = getCheckedState(mAnnotation.getDbKey());
        isNewTagView = false;
    } else {
        //rotated
        isNewTagView = savedInstanceState.getBoolean(KEY_NEWTAGVIEW, false);
        mPosition = savedInstanceState.getInt(POSITION_KEY, NOT_SET);
        mY = savedInstanceState.getInt(Y_KEY, NOT_SET);
        mSortAlpha = savedInstanceState.getBoolean(SORT_KEY, false);
        restoreCheckedState(savedInstanceState);
    }

    mTagAdapter = new TagListAdapter(context, null, mCheckedState);
    mSuggestionAdapter = new TagSuggestionAdapter(context, null, 0);

    LoaderManager lm = getLoaderManager();
    if(lm != null){
        lm.initLoader(TAGLOADERID, null, this);
    }

    this.setView(view);
    super.onCreate(savedInstanceState);
}

private void addNewTag() {
    String tag = mEditText.getText().toString().trim();
    if(!tag.equals("")){
        getDatabase();
        Integer langID = mAnnotation.getLanguageId();
        try{
            long tagID = mlDatabase.insertTag(langID, tag);
            if(mAnnotation.getDbKey().intValue() != MLDatabase.NOT_SET_INT &&
                    tagID != MLDatabase.NOT_SET_INT){
                mCheckedState.put(tagID, new CheckedState(tagID, true, true));
            }
        } catch (Exception e) {
            Log.d(TAG, "Problem saving new tag: " + tag + " : " + e.getMessage());
            e.printStackTrace();
        }
    }
}

public void onStart(){
    if(isNewTagView){
        setupNewTagDialog();
    } else {
        setupTagDialog();
    }
    restartLoader();
}

@Override
public Bundle onSaveInstanceState(){
    Bundle bundle = super.onSaveInstanceState();

    //Save What dialog we are in.
    bundle.putBoolean(KEY_NEWTAGVIEW, isNewTagView);
    bundle.putBoolean(SORT_KEY, mSortAlpha);

    //Save position
    bundle.putInt(POSITION_KEY, mListView.getFirstVisiblePosition());
    final View v = mListView.getChildAt(0);
    bundle.putInt(Y_KEY, (v == null) ? 0 : v.getTop());

    //Save Checked State
    Iterator it = mCheckedState.entrySet().iterator();
    int i = 0;
    while(it.hasNext()){
        Map.Entry pair = (Map.Entry)it.next();
        bundle.putSerializable(CHECKED_STATE_KEY + i, (CheckedState)pair.getValue());
        i++;
    }
    bundle.putInt(CHECKED_STATE_KEY, i);

    return bundle;
}

private void restoreCheckedState(Bundle bundle){
    int count = bundle.getInt(CHECKED_STATE_KEY);
    mCheckedState = new HashMap<Long, CheckedState>();
    boolean success = true;
    for(int i = 0; i < count; i++){
        CheckedState cs = (CheckedState)bundle.getSerializable(CHECKED_STATE_KEY+i);
        if(cs == null){
            success = false;
            break;
        }
        mCheckedState.put(cs.tagID(), cs);
    }
    if(!success){
        mCheckedState = getCheckedState(mAnnotation.getDbKey());
    }
}

@Override
public void onBackPressed(){
    if(isNewTagView){
        hideIMM();
        setupTagDialog();
    } else {
        this.dismiss();
    }
}

private void setupTagDialog() {
    isNewTagView = false;
    mTitle.setText(mTagTitle);
    mNewTagButton.setVisibility(View.VISIBLE);
    mSortTagButton.setVisibility(View.VISIBLE);
    mEmptyView.setVisibility(View.INVISIBLE);
    mEditText.setVisibility(View.GONE);
    mListView.setVisibility(View.GONE);
    mProgressBar.setVisibility(View.VISIBLE);
    mListView.setAdapter(mTagAdapter);
    restartLoader();
}

private void setupNewTagDialog() {
    isNewTagView = true;
    mTitle.setText(mNewTagTitle);
    mNewTagButton.setVisibility(View.INVISIBLE);
    mSortTagButton.setVisibility(View.INVISIBLE);
    mEmptyView.setVisibility(View.INVISIBLE);
    mEditText.setVisibility(View.VISIBLE);
    mListView.setVisibility(View.GONE);
    mProgressBar.setVisibility(View.VISIBLE);
    mListView.setAdapter(mSuggestionAdapter);
    restartLoader();
}

public void setAnnotation(Annotation a) {
    mAnnotation = a;
}

public void setContentViewInterface(ContentFragment contentFragment) {
    mContentFragment = contentFragment;
}

private MLDatabase getDatabase() {
    if(mlDatabase == null){
        GospelLibraryApplication app = (GospelLibraryApplication) getContext().getApplicationContext();
        mlDatabase = app.getMlDatabase();
    }
    return mlDatabase;
}

public String getFilter() {
    return mEditText.getText().toString().trim();
}

public Integer getAnnotationID(){
    if(mAnnotation != null){
        return mAnnotation.getDbKey();
    }
    return MLDatabase.NOT_SET_INT;
}

private LoaderManager getLoaderManager(){
    if(mContentFragment == null){
        Log.d(TAG, "ContentFragment is NULL!");
        return null;
    }
    return mContentFragment.getContentActivity().getSupportLoaderManager();
}

private void restartLoader(){
    LoaderManager lm = getLoaderManager();
    if(lm != null){
        lm.restartLoader(TAGLOADERID, null, this);
    }
}

private void hideIMM(){
    InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}

private HashMap<Long, CheckedState> getCheckedState(Integer annotationID) {
    HashMap<Long, CheckedState> checkedState = new HashMap<Long, CheckedState>();
    MLDatabase db = getDatabase();
    Cursor cursor = db.queryAllTagsWithAnnotation(annotationID);
    if(cursor != null){
        for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()){
            Long tagID = cursor.getLong(cursor.getColumnIndex(MLDatabase.CL_ID));
            boolean isChecked = !cursor.isNull(cursor.getColumnIndex(MLDatabase.CL_ANNOTATION));
            checkedState.put(tagID, new CheckedState(tagID, isChecked, false));
        }
    }
    return checkedState;
}

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    TagCursorLoader loader = new TagCursorLoader(getContext(), this);
    return loader;
}

@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor data) {
    if(isNewTagView) {
        mSuggestionAdapter.changeCursor(data);
        if(mListView.getAdapter() == null){
            mListView.setAdapter(mSuggestionAdapter);
        }
    } else {
        mTagAdapter.changeCursor(data);
        if(mListView.getAdapter() == null){
            mListView.setAdapter(mTagAdapter);
        }
    }
    if(mPosition != NOT_SET && mY != NOT_SET){
        mListView.setSelectionFromTop(mPosition, mY);
        mPosition = mY = NOT_SET;
    }

    if (mListView.getAdapter() != null) {
        if (mListView.getAdapter().getCount() > 0) {
            mEmptyView.setVisibility(View.INVISIBLE);
        }
        else {
            mEmptyView.setVisibility(View.VISIBLE);
        }
    }
    else {
        mEmptyView.setVisibility(View.VISIBLE);
    }
    mProgressBar.setVisibility(View.GONE);
    mListView.setVisibility(View.VISIBLE);
    mListView.invalidate();
}

@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
    if(mSuggestionAdapter != null) {
        mSuggestionAdapter.changeCursor(null);
    }
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    if(isNewTagView){
        TextView tv = (TextView)view;
        mEditText.setText(tv.getText());
        Button ok = getButton(BUTTON_POSITIVE);
        if(ok != null){
            ok.performClick();
        }
    } else {
        CheckedTextView ctv = (CheckedTextView)view;
        boolean checked = !ctv.isChecked();
        ctv.setChecked(checked);
        mCheckedState.put(id, new CheckedState(id, checked, true));
    }

}

public static class TagCursorLoader extends CursorLoader {
    private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();

    private TagDialog dialog;
    private MLDatabase mlDatabase;
    private Cursor mCursor;
    private String mFilter;
    private Integer mAnnotationID;

    // Runs on worker thread
    @Override
    public Cursor loadInBackground(){
        Cursor cursor = null;
        if(dialog.isNewTagView){
            mFilter = dialog.getFilter();
            cursor = mlDatabase.getTagSuggestions(mFilter);
        } else {
            cursor = mlDatabase.queryTags(dialog.mSortAlpha);
        }

        if(cursor != null){
            cursor.registerContentObserver(mObserver);
        }

        return cursor;

    }

    //Runs on UI thread
    @Override
    public void deliverResult(Cursor cursor){
        //Handle if canceled in the middle.
        if(isReset()){
            if(cursor != null){
                cursor.close();
            }
            return;
        }

        Cursor oldCursor = mCursor;
        mCursor = cursor;
        if(isStarted()) {
            super.deliverResult(cursor);
        }

        if(oldCursor != null && !oldCursor.equals(cursor) && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    public TagCursorLoader(Context context, TagDialog dialog) {
        super(context);
        this.dialog = dialog;
        mlDatabase = dialog.getDatabase();
    }

    @Override
    public void onStartLoading(){
        if(mCursor == null) {
            forceLoad();
        } else {
            if(dialog.isNewTagView && mFilter.equals(dialog.getFilter())) {
                deliverResult(mCursor);
            } else {
                forceLoad();
            }
        }
    }

    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }

}

/**
 * Class is used to store the temporary checked state of the tags.
 */
public class CheckedState implements Serializable {
    private static final long serialVersionUID = 1263560458217339487L;

    /**
     * @serialField
     */
    private long tagID;
    /**
     * @serialField
     */
    private boolean checked;
    /**
     * @serialField
     */
    private boolean changed;

    /**
     * Constructor for CheckedState.
     * @param tagID   The tag ID
     * @param checked The Current Checked State
     * @param changed Ture if changed in the dialog. False if pulling from database.
     */
    public CheckedState(long tagID, boolean checked, boolean changed){
        this.tagID = tagID;
        this.checked = checked;
        this.changed = changed;
    }

    public long tagID(){
        return tagID;
    }

    public boolean checked() {
        return checked;
    }

    public boolean changed() {
        return changed;
    }
  }
}
Ge3ng
  • 1,875
  • 1
  • 24
  • 38
  • [This comment](http://stackoverflow.com/a/9063814/1348379) by CommonsWare helped me out when I was having similar issues. I don't think you should be swapping out adapters like you are doing. Cursor swapping via ````swapCursor(cursor)````- yes, list adapters- no. Create a separate fragment with another loader for that information and initialise both the adapter (via ````setAdapter(...)````) and loader (````initLoader````) during onActivityCreated. – BrantApps May 29 '12 at 17:26
  • @OceanLife I am not using an activity, I am doing this all in an extended AlertDialog. – Ge3ng May 29 '12 at 17:35
  • have you tried initLoader(...).forceLoad()? I had some issues with ICS too, and that managed to fix them. – Alex Curran May 29 '12 at 17:43
  • @Espiandev I have tried that and it doesn't fix the issue. – Ge3ng May 29 '12 at 17:45
  • @Ge3ng nice! Entertained the idea of using a dialogfragment and just styling it like an alert dialog with its own XML layout and title? – BrantApps May 29 '12 at 18:51

2 Answers2

2

Note I didn't add my XML before. That is where the issue resides.

andriod:animateLayoutChanges 

doesn't work with what I was trying to do.

Once I removed that from my XML it worked like a charm.

Ge3ng
  • 1,875
  • 1
  • 24
  • 38
1

In most examples that I see, the you create your adapter instance once and set it into the ListView when you view is created, and then call getLoaderManager().initLoader().

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

Then in the onLoadFinished() method you call swapCursor() which automatically refreshes the ListView.

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in.  (The framework will take care of closing the
    // old cursor once we return.)
    mAdapter.swapCursor(data);
}

The above code was copied from the Loaders documentation http://developer.android.com/guide/topics/fundamentals/loaders.html

UPDATE: The documentation talks about using Loaders for Activities and Fragments, but doesn't mention using Dialogs. I'm guessing that if getLoaderManager() exists you are fine, but if you are not using the LoaderManager and you are running the Loader manually yourself, then I would think that you'd need to ensure that when you call swapCursor() or setAdapter() that you are doing this on the UI thread. Sometimes the easiest way to ensure this, is to call

getListView().post(new Runnable() {
   public void run() {
      // so the setAdapter() or swapCursor() here
   }
});

I have run into cases myself where I've updated a ListView in the background and it doesn't reflect as being updated until I rotate the device, because the UI wasn't updated on the UI thread.

stuckless
  • 6,515
  • 2
  • 19
  • 27
  • I am using a LoaderManager so I have onLoadFinished and that is where I change the cursor. It is called but the UI doesn't update. It is weird because when I switch to back to the first adapter the UI updates and works like it is supposed too. – Ge3ng May 30 '12 at 14:20
  • You mentioned though that you are using this in an `AlertDialog`. `AlertDialog` does not extend `Fragment` or `Activity`, so how are you getting a reference to the `LoaderManager`? – stuckless May 30 '12 at 14:31
  • I am embedding my AlertDialog in a DialogFragment and that is how I am accessing the LoaderManager. – Ge3ng May 30 '12 at 14:36
  • Do you have code for that... What i'm wondering is that if the `LoaderManager` when executing your `onLoadFinished`, is doing so in the `Fragment` UI and not the `Dialog` ui. Did you try putting the `getListView().post()` example above in your `onLoadFinished`? Again, if you update a `ListView` from a different Thread, then it probably won't reflect the updates. (This has happened to me a couple of times, although not with using `LoaderManagers`) – stuckless May 30 '12 at 14:44
  • I tried the .post() method but it crashes when I switch to the adapters the second time. They worked fine the second time it was just the first time they were opened that this is an issue. – Ge3ng May 30 '12 at 15:14
  • In Looking at your code, you are checking if `mListView.getAdapter()` is null, and it doesn't appear that it is ever null, since you explicitly call `mListView.setAdapter()` in the `setup` methods. Maybe it's as simple as removing the null checks?? – stuckless May 30 '12 at 15:26
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/11945/discussion-between-ge3ng-and-stuckless) – Ge3ng May 30 '12 at 16:04