Having trouble restoring state on listview

核能气质少年 提交于 2020-01-06 19:57:28

问题


I have an app that allows the user to select an option and a certain list is displayed in a listview. I'm having issues with getting it to save and restore state. I have a list_mode that gets set depending on which option the user selects. so far I have it saving the state of the adapter but restoring it is causing an issue ( I know it's saving because when I examine customAdapter.onRestoreInstanceState(savedInstanceState) in the onCreate it's showing the right items based on selection but I'm having a hard time restoring it to the list view. It will crash on restore. It crashes on getAllItems.clear and .addAll. I have even hard coded this to one of the lists but it didn't work either. When I comment that line out then it crashes in the main activity. I'm a noob when it comes to Java and Android and am still learning. I do not know how close or how far off I am. Can anyone provide insight on what I'm doing wrong and how I can get it to work? Thanks

List Adapter:

public class ListAdapter extends BaseAdapter {
private static final String KEY_ADAPTER_STATE = "ListAdapter.KEY_ADAPTER_STATE";
public enum ListMode {
    IMAGES_AND_TEXT,
    IMAGES_ONLY,
    TEXT_ONLY
}

private ListMode mListMode = ListMode.IMAGES_AND_TEXT;

private ArrayList<Item> mItems;

private ArrayList<Item> mImages;

private ArrayList<Item> mTexts;

@Override
public int getCount() {
    switch (mListMode) {
        case IMAGES_AND_TEXT:
            return mItems == null ? 0 : mItems.size();
        case IMAGES_ONLY:
            return mImages == null ? 0 : mImages.size();
        case TEXT_ONLY:
            return mTexts == null ? 0 : mTexts.size();
    }
    return 0;
}

@Override
public Item getItem(int position) {
    switch (mListMode) {
        case IMAGES_AND_TEXT:
            return mItems == null ? null : mItems.get(position);
        case IMAGES_ONLY:
            return mImages == null ? null : mImages.get(position);
        case TEXT_ONLY:
            return mTexts == null ? null : mTexts.get(position);

    }
    return null;
}
public ArrayList getAllItems() {
    switch (mListMode) {
        case IMAGES_AND_TEXT:
            return mItems;
        case IMAGES_ONLY:
            return mImages;
        case TEXT_ONLY:
            return  mTexts;

    }
    return null;
}

@Override
public long getItemId(int position) {
    return position;    // not really used
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    View v = null;
    TextView tn = null;
    ImageView img = null;

    if (convertView == null) {
        LayoutInflater vi;
        vi = LayoutInflater.from(parent.getContext());
        v = vi.inflate(R.layout.list, null);
    } else {
        v=convertView;
    }

    Item p = getItem(position);
    tn = (TextView) v.findViewById(R.id.tvText);
    img = (ImageView) v.findViewById(R.id.thumbnail);
    if (p.getmType().equals("image")) {
        img.setVisibility(View.VISIBLE);
        Picasso.with(parent.getContext()).load(p.getmData()).error((R.drawable.placeholder_error)).placeholder(R.drawable.placeholder).resize(90,0).into(img);
        tn.setText("ID: " + p.getmID()+"\nTYPE: " + p.getmType() +"\nDate: " + p.getmDate()+ "\nImage URL: " +  p.getmData());
    } else {
        img.setVisibility(View.GONE);
        tn.setText("ID: " + p.getmID()+"\nTYPE: " + p.getmType() +"\nDate: " + p.getmDate()+ "\nText Data: " +  p.getmData());
    }
    return v;
}


public void setListMode(ListMode listMode) {
    mListMode = listMode;
    notifyDataSetChanged();
}

public void setItems(JSONArray jsonArray) throws JSONException {

    mItems = new ArrayList<>();
    mImages = new ArrayList<>();
    mTexts = new ArrayList<>();
    for (int i = 0; i < jsonArray.length(); i++) {
        Item item = new Item((JSONObject) jsonArray.get(i));
        mItems.add(item);
        if (item.getmType().equals("image")) {
            mImages.add(item);
        }
        if (item.getmType().equals("text")) {
            mTexts.add(item);
        }
    }

    notifyDataSetChanged();
}
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putParcelableArrayList("list_items", getAllItems());
}

public void onRestoreInstanceState(Bundle savedInstanceState) {
    if (savedInstanceState.containsKey("list_items")) {
        //ArrayList<Item> objects = savedInstanceState.getParcelableArrayList(KEY_ADAPTER_STATE);
        //getAllItems().clear();
        //getAllItems().addAll(objects);
        //mImages.clear();
        //mImages.addAll(objects);
    }
}

}

Main Activity:

myListView = (ListView) findViewById(R.id.listViewID);
    customAdapter = new ListAdapter();
    if(savedInstanceState!=null) {
        //When I put a breakpoint here I see it's getting the correct list
        customAdapter.onRestoreInstanceState(savedInstanceState);
    }

    if (savedInstanceState != null && savedInstanceState.containsKey("list_items")) {
        //When I remove the .clear and .addAll it then crashes on this line
        myListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_items"));
        myListView.setAdapter(customAdapter);
    }
    else{
        if (isNetworkAvailable()) {
            getData theJsonData = new getData();
            theJsonData.execute();
        }
        else{
            Toast.makeText(getApplicationContext(), "No internet connection", Toast.LENGTH_SHORT).show();
            tvNoInet.setVisibility(View.VISIBLE);
        }
    }
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    //Not sure what I should put here or if what I have is right
   customAdapter.onSaveInstanceState(savedInstanceState);

}
//This is the code that sets the list_mode when the user selects an option
if (id == R.id.all) {
        item.setChecked(true);
        customAdapter.setListMode(ListAdapter.ListMode.IMAGES_AND_TEXT);
        myListView.setSelectionAfterHeaderView();
        return true;

    }
    if (id == R.id.images) {
        item.setChecked(true);
        customAdapter.setListMode(ListAdapter.ListMode.IMAGES_ONLY);
        myListView.setSelectionAfterHeaderView();
        return true;

    }
    if (id == R.id.text) {
        item.setChecked(true);
        customAdapter.setListMode(ListAdapter.ListMode.TEXT_ONLY);
        myListView.setSelectionAfterHeaderView();
        return true;
    }

And my Item class:

public class Item implements Parcelable{
private String mID;
private String mType;
private String mDate;
private String mData;

public Item(String mID,String mType, String mDate, String mData)  {
    this.mType = mType;
    this.mID = mID;
    this.mDate = mDate;
    this.mData = mData;
}
public Item(JSONObject jsonItem) throws JSONException {
    String itemID=null;
    String itemType=null;
    String itemDate=null;
    String itemData=null;

    if (jsonItem.has("id")) {
        itemID=jsonItem.getString("id");
    }
    if (jsonItem.has("type")) {
        itemType=jsonItem.getString("type");
    }
    if (jsonItem.has("date")){
        itemDate=jsonItem.getString("date");
    }
    if (jsonItem.has("data")){
        itemData=jsonItem.getString("data");
    }
    this.mID=itemID;
    this.mType=itemType;
    this.mDate=itemDate;
    this.mData=itemData;
}


protected Item(Parcel in) {
    String[] data = new String[4];
    in.readStringArray(data);
    this.mID = data[0];
    this.mType = data[1];
    this.mDate = data[2];
    this.mData = data[3];

}

public static final Creator<Item> CREATOR = new Creator<Item>() {
    @Override
    public Item createFromParcel(Parcel in) {
        return new Item(in);
    }

    @Override
    public Item[] newArray(int size) {
        return new Item[size];
    }
};

public String getmID() {
    return mID;
}

public String getmType() {
    return mType;
}

public String getmDate() {
    return mDate;
}

public String getmData() {
    return mData;
}

@Override
public String toString() {
    return "{" +
            "ID='" + mID + '\'' +
            ", Type='" + mType + '\'' +
            ", Date='" + mDate + '\'' +
            ", Data='" + mData + '\'' +
            '}';
}

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {

    String[] data = new String[4];
    data[0] = mID;
    data[1] = mType;
    data[2] = mDate;
    data[3] = mData;
    dest.writeStringArray(data);
}
}

UPDATE: This is what the code looks like now. When I select a certain list and then rotate the screen it's working. It's staying on the currently selected list. Now the issue is when they select a new list after the device has been rotated the listview is completely blank

if(savedInstanceState!=null) {
        customAdapter.onRestoreInstanceState(savedInstanceState);

        if (savedInstanceState.containsKey(KEY_LIST_VIEW_STATE)) {
            myListView.onRestoreInstanceState(savedInstanceState.getParcelable(KEY_LIST_VIEW_STATE));
            myListView.setAdapter(customAdapter);
        }

    }

    else{
        if (isNetworkAvailable()) {
            getData theJsonData = new getData();
            theJsonData.execute();
            tvNoInet.setVisibility(View.GONE);
            btnRetry.setVisibility(View.GONE);
        } else {
            Toast.makeText(getApplicationContext(), "No internet connection", Toast.LENGTH_SHORT).show();
            tvNoInet.setVisibility(View.VISIBLE);
            btnRetry.setVisibility(View.VISIBLE);
        }
    }

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    customAdapter.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putParcelable(KEY_LIST_VIEW_STATE, myListView.onSaveInstanceState());


}

and my updated list adapter:

 private ListMode mListMode = ListMode.IMAGES_AND_TEXT;

private ArrayList<Item> mItems= new ArrayList<>();

private ArrayList<Item> mImages= new ArrayList<>();

private ArrayList<Item> mTexts= new ArrayList<>();

   public void onSaveInstanceState(Bundle savedInstanceState) {
    //savedInstanceState.putParcelableArrayList("list_items", getAllItems());
    savedInstanceState.putParcelableArrayList(KEY_ADAPTER_STATE, getAllItems());
}

public void onRestoreInstanceState(Bundle savedInstanceState) {
    if (savedInstanceState.containsKey(KEY_ADAPTER_STATE)) {
        ArrayList<Item> objects = savedInstanceState.getParcelableArrayList(KEY_ADAPTER_STATE);
        getAllItems().clear();
        getAllItems().addAll(objects);
        //mImages.clear();
        //mImages.addAll(objects);
    }
}

And this is the code in the options menu that switches the list. If I haven't rotated the screen yet this works. When I rotate the screen and select a different list the listview is then blank. If I select "All" which should display All items then whatever list I had selected before if restored. So If I selected Images Only at first, rotate the screen everything is displayed right, if I rotate the screen back and select Text Only or Images Only again, the list is blank, if I select "All" then whatever list I picked the first time (in this case Images Only) is displayed

if (id == R.id.all) {
        item.setChecked(true);
        customAdapter.setListMode(ListAdapter.ListMode.IMAGES_AND_TEXT);
        myListView.setSelectionAfterHeaderView();
        return true;

    }
    if (id == R.id.images) {
        item.setChecked(true);
        customAdapter.setListMode(ListAdapter.ListMode.IMAGES_ONLY);
        myListView.setSelectionAfterHeaderView();
        return true;

    }
    if (id == R.id.text) {
        item.setChecked(true);
        customAdapter.setListMode(ListAdapter.ListMode.TEXT_ONLY);
        myListView.setSelectionAfterHeaderView();
        return true;
    }

回答1:


Well, since I got you into this mess.... :)

Something I do in situations like this is to pass in the saved instance state Bundle to the constructor for the adapter. Since you made your Item class Parcelable, everything else should be easy.

    public ListAdapter(Bundle savedInstanceState) {

        if (savedInstanceState != null) {

            int ordinal = savedInstanceState.getInt("adapter_mode", 0);
            mListMode = ListMode.values()[ordinal];

            ArrayList<Item> items = 
                    savedInstanceState.getParcelableArrayList(KEY_ADAPTER_STATE);
            if (items != null) {
                setItems(items);
            }
        }
    }


    public void onSaveInstanceState(Bundle outState) {
        outState.putInt("adapter_mode", mListMode.ordinal());
        outState.putParcelableArrayList(KEY_ADAPTER_STATE, mItems);
    }

So with the saved instance state in the constructor, you don't need an explicit method to restore the adapter state.

    public void setItems(JSONArray jsonArray) throws JSONException {

        List<Item> items = new ArrayList<>();
        for (int i = 0; i < jsonArray.length(); i++) {
            items.add(new Item((JSONObject) jsonArray.get(i)));
        }
        setItems(items);
    }

    private void setItems(List<Item> items) {

        for (Item item : items) {
            mItems.add(item);
            if (item.getmType().equals("image")) {
                mImages.add(item);
            }
            if (item.getmType().equals("text")) {
                mTexts.add(item);
            }
        }

        notifyDataSetChanged();
     }



回答2:


Your app crashes if you call ListAdapter.getAllItems().clear() without calling ListAdapter.setItems() before

Cause: ListAdapter.getAllItems() may return null if mItems, mImages, mTexts are not initialized.

One workaround may be

public class ListAdapter extends ... {
    ...
    private ArrayList<Item> mItems = new ArrayList<>();
    private ArrayList<Item> mImages = new ArrayList<>();
    private ArrayList<Item> mTexts = new ArrayList<>();
    ...
    }


来源:https://stackoverflow.com/questions/35298081/having-trouble-restoring-state-on-listview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!