问题
I'm doing some test after change device configuration (change language, orientation, etc), and i notice that after this, the method "notifyDataSetChanged()" is not working.
The action example:
I'm calling updateList() everytime i do an action like delete, save, etc. The user click a delete button, a DialogFragment is shown, "Are you sure you want to delete?", when i change the orientation, or the language, or any configuration of the device and then click "yes" on the Dialog, the data is removed, but the list doesn't update. I need to quit the activity, then go back to see the alteration.
BookAdapter:
public void updateList(ArrayList<Book> books) {
bookList = books;
notifyDataSetChanged();
}
What can i do to make it works after the configuration change?
Edit:
BookAdapter Constructor:
public BookAdapter(Context c, ArrayList<Book> books) {
context = c;
bookList = books
bookDAO = BookDAO.getInstance(context);
}
BookFragment:
public class BookFragment extends Fragment {
private BookDAO bookDAO;
private BookAdapter bookAdapter;
private ListView listBook;
private View view;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bookDAO = bookDAO.getInstance(getActivity());
view = inflater.inflate(R.layout.book_tab, container, false);
ArrayList<Book> listBook = null;
try {
llistBook = bookDAO.getAll();
} catch (Exception e) {
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
return view;
}
bookAdapter = new BookAdapter(getActivity(), listBook);
listBook = (ListView)view.findViewById(R.id.listBook);
listBook.setAdapter(bookAdapter);
return view;
}
}
回答1:
The problem occurs because every time your rotate, change language, etc... the activity is recreated and your fragments are also recreated (new instance), so the notififyDataSetChanged is actually notifying the old instances of your fragments. A solution for that would be. Make your fragments static. Then you create some refresh method for your fragments, and called it when you press yes for your dialog.
In your activity you should have something like this.
private static BookFragment bookFragment;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
if (fragment1 == null) {
bookFragment = new BookFragment();
}
...
}
Create some interface like:
public interface Refreshable {
public void refresh();
}
Then implement this interface all your fragments. In the method that is called when the dialog is answered positively you should call
...
fragment.refresh();
...
Inside the refresh method of you fragment you can call its adapter method updateList(...)
Might not be the prettier solution, but it works....
Why this happen... Google's Android Dev Team might know.
回答2:
You can try implementing BookAdapter
as a Singleton to confirm that you are not calling updateList(..)
from a stale reference.
Changes that you will need to make:
// I am assuming that you are using a BaseAdapter because
// BookAdapter's constructor that you provided in the code above
// does not contain a call to super(....)
public class BookAdapter extends BaseAdapter {
private static BookAdapter mAdapter;
private Context context;
private static ArrayList<Book> bookList;
private BookDAO bookDAO;
// To keep at most one instance of BookAdapter
public static BookAdapter getInstance(Context con, ArrayList<Book> books) {
// If an instance exists, return it
if (mAdapter != null) {
bookList = books;
return mAdapter;
}
// Else, craete a new instance
mAdapter = new MyAdapter(con, books);
return mAdapter;
}
// BookAdapter's only constructor is declared as private to restrict access
private BookAdapter(Context con, ArrayList<Book> books) {
context = con;
bookList = books;
bookDAO = BookDAO.getInstance(context);
}
public void updateList(ArrayList<Book> books) {
bookList = books;
notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Retrieve object
Book bookItem = bookList.get(position);
....
....
}
}
This is how the Fragment's onCreateView will change:
bookAdapter = BookAdapter.getInstance(getActivity(), listBook);
Code that will be executed when the user presses yes to Are you sure you want to delete?
:
// Remove entry from bookDAO
// Remove entry from listBook
// OR update listBook:
try {
listBook = bookDAO.getAll();
} catch (Exception e) {
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
}
// Assertion: "listBook" does not contain the
// item that was just deleted from "bookDAO"
// Update ListView's contents
bookAdapter.updateList(listBook);
回答3:
I am using an ArrayList of Strings in my GridViewAdapter which extends BaseAdapter.
so in case i changed the data concerning the List, i cann notifyDataSetChanged() on the Adapter. I dont really see the point why you cant call it?
So what i would do is ovverride this method, and just call notifyDataSetChanged()
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
adapter.notifyDataSetChanged();
}
Besides that, does your Book data chnage based on your configuration/orientation?
回答4:
Try using the same list for the whole adapter lifecycle, and change only its content:
public class BookAdapter extends ArrayAdapter<Book> {
private final booklist;
// ..
public BookAdapter(Context c, ArrayList<Book> books) {
super(c, R.layout.yourlayout, books);
context = c;
bookList = books
bookDAO = BookDAO.getInstance(context);
}
public void updateList(ArrayList<Book> books) {
bookList.clear();
boolList.addAll(books);
notifyDataSetChanged();
}
// ..
}
回答5:
Why are you keeping your View as a data member?
When keeping a view across configuration changes, you hold a reference to the previous instance, before the configuration change was happening. Since it's the view holding your list - it may miss data updates.
Try to remove this field, and the use of it, and see if the list now updates.
回答6:
I think the problem comes from the fact that any configuration change such as the orientation will restart your current Activity
.
Because of this, I guess some parts of your code are still referencing the previous activity that does not exist anymore and the notifyDataSetChanged
is not working anymore.
There are 2 things you can quickly try:
Add this line in the manifest file for your activity:
android:configChanges="orientation|locale"
. This change means to the system that you will handle yourself the changes to do during orientation or language changes. Therefore, the app will not recreate the activity by itself anymore (so the activity should work the same).The other trick can be to add this line at the beginning of the function
onCreateView
:setRetainInstance(true);
. This line will retain the fragment state during configuration changes as the documentation explains:
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
- onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
- onCreate(Bundle) will not be called since the fragment is not being re-created.
- onAttach(Activity) and onActivityCreated(Bundle) will still be called.
Just be informed that as explained the Fragment
lifecycle will change a little.
3) Last option could be to retain the activity state using onSaveInstanceState
and onRestoreInstanceState
as explained in details in this answer: https://stackoverflow.com/a/151940/2206688
来源:https://stackoverflow.com/questions/18773251/notifydatasetchanged-after-configuration-change