问题
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