I'm struggling to recover my position in a listview on screen rotation configuration change.
Amongst the many things I've tried I came to this:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState == null) {
...
mVisibleItem = -1;
} else {
if (savedInstanceState.containsKey(LV_VISIBLE_ITEM)) {
mVisibleItem = savedInstanceState.getInt(LV_VISIBLE_ITEM);
}
}
setRetainInstance(true);
}
and here I'm trying to set the position in the listview
#Override
public void onResume() {
super.onResume();
if (mVisibleItem > 0) {
mlvDictionaryIndex.setSelectionFromTop(mVisibleItem, 0);
}
}
However, much to my surprise, after rotating the screen and watching mVisibleItem gets set with the correct value, in onResume I see that mVisibleItem equals -1. How come?
use onSavedInstanceState to write in the bundle the returned value of ListView.onSaveInstanceState(), and restored it onActivityCreated
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mListView != null) {
outState.putParcelable(LISTVIEW_INTERNAL_STATE_KEY, mListView.onSaveInstanceState());
}
}
after the data are reload then you can call
mListView.onRestoreInstanceState(savedInstanceState.getParcelable(LISTVIEW_INTERNAL_STATE_KEY));
Override onSaveInstanceState such as below"
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt("pos", pos);
}
Then in your onCreate method have read the savedInstanceState to check if this is an orientation change or a new activity.
private int pos = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
//This is a new activity
}else{
pos = savedInstanceState.getInt("");
}
Now you have the position in the list, and you can scroll to this in configuration change.
Maybe after the data in the listview is reloaded,the code below will work.
mlvDictionaryIndex.setSelectionFromTop(mVisibleItem, 0);
So you can use post() method, just like below:
post(new Runnable() {
public void run() {
mlvDictionaryIndex.setSelectionFromTop(mVisibleItem, 0);
}
});
Related
I have simple Fragment and I use RecylerView to list my elements. I want to keep scroll position when I change screen rotation.
Regarding the answer here I added these lines to my Fragment code;
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", recyclerView.getVerticalScrollbarPosition());
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
recyclerView.scrollToPosition(savedInstanceState.getInt("position"));
}
}
But I have a special case here. Because I have to refresh my adapter when I rotate my screen. I use these lines to refresh:
#Override
public void onConfigurationChanged(#NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
refreshAdapter();
}
private void refreshAdapter() {
if (adapter != null) {
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
adapter.notifyDataSetChanged();
}
}
When I comment out refreshAdapter(); line, I can keep my scroll position. But I have to use refreshAdapter(); line.
How can I keep my scroll position with these methods ?
I would like to not reload the RecyclerView after screen rotation from portrait to landscape.
I'm using onSaveInstanceState to save list from adapter, and restore it inside onActivityCreated
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(mAdapter != null) {
outState.putParcelableArrayList("key", mAdapter.getOriginalList());
}
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(savedInstanceState != null) {
mList = savedInstanceState.getParcelableArrayList("key");
mAdapter.notifyDataSetChanged();
}
}
But there is problem, the RecyclerView is reconstructed after each screen rotation. What is wrong ?
You need to set adapter on configuration changed programetically then only it can be possible to set data without reloading.
Something like below,
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.main);
if(mAdapter != null)
your_recycle_view.setAdapter(mAdapter);
}
So in order to save the instance of my Fragment, I use a Bundle which I save in onSaveInstanceState and restore in onActivityCreated. Now I'm wondering what the best practice is:
1) Have a bundle where you store variables in, and use/update as required in your code. This way saving and restoring states' code is very short. Something like this:
private Bundle mDataValues = new Bundle();
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if ( (savedInstanceState != null) && (savedInstanceState.getBundle("mDataValues") != null) ) {
this.mDataValues = savedInstanceState.getBundle("mDataValues");
}
public void someFunction() {
if (this.mDataValues == null) {
// Should not be possible on the times we call this function
return;
}
usernameView.setText(mDataValues.getString(Constants.BUNDLE_KEY_USERNAME));
personnameView.setText(mDataValues.getString(Constants.BUNDLE_KEY_PERSONNAME));
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putBundle("mDataValues", mDataValues);
}
2) Have a bunch of variables declared in the fragment, and use these in your code. Something like this:
private String mUsername;
private String mPersonname;
#Override
public void onActivtyCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if ( savedInstanceState != null ) {
mUsername = savedInstanceState.getString("username");
mPersonname = savedInstanceState.getString("personname");
}
}
public void someFunction() {
usernameView.setText(mUsername);
personnameView.setText(mPersonname);
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("username", mUsername);
outState.putString("personname", mPersonname);
}
The amount of variables above is of course a lot less than in the actual code written. So what is the best practice? Or are both viable in specific situations?
I have fragment contains recyclerview in main activity
Fragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = inflater.inflate(R.layout.activity_main, container, false);
initInstances(view);
setRetainInstance(true);
return view;
}
private void initInstances(View view) {
mRecyclerView = (RecyclerView) view.findViewById(R.id.dialogList);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
adapter = new MyAdapter(items);
mRecyclerView.setAdapter(mAdapter);
}
MainActivity.java
in onCreate method:
myFragment = (MyFragment) getSupportFragmentManager().findFragmentByTag(MyFragment.TAG);
if (MyFragment == null) {
MyFragment = new MyFragment();
getSupportFragmentManager().beginTransaction().add(R.id.content_frame,
myFragment, MyFragment.TAG).commit();
}
But when orientation changed, my RecyclerView redrawn.
I've tried save state with overriding onSaveInstanceState and onRestoreInstanceState like this How to prevent custom views from losing state across screen orientation changes or Saving and restoring view state android, but to no avail for me
How to correctly save state of RecyclerView and adapter's items when orientation change?
SOLUTION:
My fragment extends from BaseFragment with some methods:
public class BaseFragment extends Fragment{
Bundle savedState;
public BaseFragment() {
super();
if (getArguments() == null)
setArguments(new Bundle());
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!restoreStateFromArguments()) {
onFirstTimeLaunched();
}
}
protected void onFirstTimeLaunched() {
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveStateToArguments();
}
public void saveStateToArguments() {
if (getView() != null)
savedState = saveState();
if (savedState != null) {
Bundle b = getArguments();
b.putBundle("internalSavedViewState8954201239547", savedState);
}
}
private boolean restoreStateFromArguments() {
Bundle b = getArguments();
savedState = b.getBundle("internalSavedViewState8954201239547");
if (savedState != null) {
onRestoreState(savedState);
return true;
}
return false;
}
private Bundle saveState() {
Bundle state = new Bundle();
onSaveState(state);
return state;
}
protected void onRestoreState(Bundle savedInstanceState) {
}
protected void onSaveState(Bundle outState) {
}
#Override
public void onStart() {
super.onStart();
}
#Override
public void onStop() {
super.onStop();
}
#Override
public void onDestroyView() {
super.onDestroyView();
saveStateToArguments();
}
In my fragment I override methods and save state of mLayoutManager
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
protected void onSaveState(Bundle outState) {
super.onSaveState(outState);
outState.putParcelable("myState", mLayoutManager.onSaveInstanceState());
}
#Override
protected void onRestoreState(Bundle savedInstanceState) {
super.onRestoreState(savedInstanceState);
mLayoutManager.onRestoreInstanceState(savedInstanceState.getParcelable("myState"));
}
I searched how to get the scroll position of the RecyclerView, but didn't see where I can retrieve it, so I think the best solution is to handle rotation changes directly in your java code, and preventing your activity to restart on orientation change.
To prevent restarting on orientation change, you can follow this solution.
After preventing your Activity from restart, you can build 2 xml layout files, one for portrait mode, and one for landscape mode, you add a FrameLayout in both xml files, holding the place for your RecyclerView.
When the onConfigurationChange(Configuration newConfig) method is called, you call setContentView(int layoutFile) with the appropriate LayoutFile following the new orientation, and add your RecyclerView you are holding in a member of your class in the FrameLayout.
I am trying to save fragment state. I have an activity and several fragments. The sequence of actions: add first fragment, change view manually (make visibility of first LinearLayout GONE and second LinearLayout VISIBLE), detach fragment, add another one, detach it and again attach first fragment.
Adding/attaching/detaching works good but setRetainInstanse(true) saves only initial fragment state.
Finally I get first LinearLayout visible at my fragment (instead of second) so I've tried to make it by hands but it doesn't work:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(BUNDLE_IS_LOADING)) {
if (savedInstanceState.getBoolean(BUNDLE_IS_LOADING)) {
mBlockContent.setVisibility(View.GONE);
mBlockProgress.setVisibility(View.VISIBLE);
} else {
mBlockContent.setVisibility(View.VISIBLE);
mBlockProgress.setVisibility(View.GONE);
}
}
}
}
setRetainInstance(true);
}
#Override
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
b.putBoolean(BUNDLE_IS_LOADING,
mBlockProgress.getVisibility() == View.VISIBLE);
}
I use compatibility library rev. 11.
Solution for me:
private boolean isProgressing;
private void saveViewsState() {
isProgressing = mBlockProgress.getVisibility() == View.VISIBLE;
}
private void switchToProgress() {
mBlockContent.setVisibility(View.GONE);
mBlockProgress.setVisibility(View.VISIBLE);
}
private void switchToContent() {
mBlockContent.setVisibility(View.VISIBLE);
mBlockProgress.setVisibility(View.GONE);
}
#Override
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
saveViewsState();
}
#Override
public void onPause() {
super.onPause();
saveViewsState();
}
#Override
public void onResume() {
super.onResume();
if (isProgressing) {
switchToProgress();
} else {
switchToContent();
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (isProgressing) {
switchToProgress();
} else {
switchToContent();
}
}