I have an activity with a ViewPager and 3 fragments called A,B,C. A has a recyclerView populated by cardView and each card implements and OnClickListener which leads to a new Activity D. I want to be able to save the recyclerView scroll position when switching in tabs and when coming back from the opened activity D.
what i did so far in Fragment A (the one containing the RecyclerView looks like this :
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
recyclerView = (RecyclerView)inflater.inflate(R.layout.event_fragment_layout, container,false);
linearLayoutManager = new LinearLayoutManager((getActivity()));
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setHasFixedSize(true);
return recyclerView;
}
public void onStart(){
//This is where my adapter is created and attached to the recyclerView
recyclerView.setAdapter(myadapter)
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(RECYCLER_STATE,linearLayoutManager.onSaveInstanceState());
String saved = linearLayoutManager.onSaveInstanceState().toString();
Toast.makeText(getContext(),"Saving Instance State",Toast.LENGTH_SHORT).show();
Log.d("SAVE_CHECK ", saved);
}
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
if(savedInstanceState!=null) {
Parcelable savedRecyclerViewState = savedInstanceState.getParcelable(RECYCLER_STATE);
if(savedRecyclerViewState!=null) {
linearLayoutManager.onRestoreInstanceState(savedRecyclerViewState);
Toast.makeText(getContext(), "Restoring Instance State", Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(getContext(), "SAVED STATE IS NULL", Toast.LENGTH_SHORT).show();
}
}
super.onViewStateRestored(savedInstanceState);
}
I'm displaying both the Toast, so the block is executing (and that means the savedRecyclerViewState is not null) but the recyclerView always starts from the first Card no matter what. Also when i open a new Activity by clicking on a card and then come back to Fragment A i get the Toast notificaton that the state is being saved but not the one in onViewRestored.
What is basically happening from my understanding is that my linearLayoutManager's state gets saved but then i'm somehow failing to pass the info to restore the scrolling position. What am i doing wrong ?
SOLVED: solved it by myself but this might help some one it the future.
TL;DR -> just declare the adapter in the onCreateView and then call
yourRecyclerView.setAdapter(yourAdapter) both in OnCreateView and OnResume(),rest of the code remains the same
What happened then ? Simply enough i was correctly saving the linearLayoutManager state in onSavedInstanceState() and then correctly restoring it in OnCreate but i was unable to notice any difference it since the saved state state was being "overwritten" by setting my adapter in the OnStart.
In steps :
1)fragment activity calls onSavedInstanceState and saves the LinearLayoutManager of my RecyclerViewin a Parcelable.
2)the app goes back to the fragment and calls onViewStateRestored()/OnCreateView(),there we read from the parcelable and restore our recyclerViewState
3)Now the problem OnStart() gets called and of course this is where my adapter is declared and then set to the recyclerView. Now the old recyclerView(the one with the saved state) is gone and is replaced by a fresh new recyclerView with no record of the previous one.
So just by simply setting the adapter in the OnResume() method you'll solve the problem.
I guess you could also solve it by leaving the code in OnStart(), store the old recyclerView state and then passing it to the new recyclerView at the end of OnStart() but it's not a really clean solution
Related
I'm trying to use this tutorial https://panavtec.me/retain-restore-recycler-view-scroll-position (which I have already used successfully in other projects) to restore the scroll state of a RecyclerView in a fragment after orientation change.The saving and restoring itself works, the data is there... but it seems to be wrong.
I debugged it and when saving in onSaveInstanceState of the fragment...
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable rvState = mRecyclerView.getLayoutManager().onSaveInstanceState();
outState.putParcelable(INSTANCE_STATE_RV_STATE, rvState);
outState.putParcelableArrayList(INSTANCE_STATE_ENTRIES, (ArrayList<? extends Parcelable>) mAdapter.getViewItems());
}
...I investigated the rvState and compared it to the state I get in the other app where scroll state saving in a RecyclerView (in an activity though...) worked as intended.
I know these values from the debugger won't help too much, but I can see that for the app where it's working, I get a savedInstanceState in onCreate that shows an anchor offset and an anchor position, both != 0, in the debugger. When restoring this state to the RecyclerView, the scroll state is correctly restored.
In the app with the fragment, the layout manager's save state shows these values both as 0 in the debugger (no matter how far down I scroll), not only at restore, but already when saving it.
So to me it looks like the scroll postion (or anchor position, whatever) can't be correctly determined. So of course when restoring this state it doesn't scroll anywhere.
In both cases I'm using the GridLayoutManager.
Anything obvious I'm doing wrong or that I forgot? I'm not really sure what code to post, because it seems all very straightforward...
I add the fragment in the activity's onCreate using the SupportFragmentManger, but only if there is no saved instance state, in order to not overwrite the fragment present.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_container, MyFragment.newInstance())
.commit();
}
}
In the fragment I save the state in onSaveInstanceState (see first method posted) and restore it in the onCreateView
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_entries, container, false);
mRecyclerView = v.findViewById(R.id.rv_entries);
mProgressBar = v.findViewById(R.id.pb_loading);
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getContext(), getSpanCount(), LinearLayoutManager.VERTICAL, false);
mAdapter = new MyAdapter(this);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setAdapter(mAdapter);
if(savedInstanceState != null) {
mSavedEntries = savedInstanceState.getParcelableArrayList(INSTANCE_STATE_ENTRIES);
mRecyclerViewState = savedInstanceState.getParcelable(INSTANCE_STATE_RV_STATE);
if (mSavedEntries != null) {
mAdapter.setViewItems(mSavedEntries);
}
if(mRecyclerViewState != null) {
layoutManager.onRestoreInstanceState(mRecyclerViewState);
}
}
(...)
return v;
}
Any hint would be appreciated...
EDIT:
Since Cheticamp asked: the version I'm using as declared in my gradle file is recyclerview-v7:26.1.0 and when I remove all of the save instance state stuff, the RecyclerView doesn't restore its state on its own. I also tried leaving in only the part that restores the entries. This doesn't change anything either.
I had the same problem with a LinearLayoutManager and found that it was because I was using a NestedScrollView before my RecyclerView in XML.
I have implemented some fragments which are hosted by a single Activity (they're in the same activity). In one of these fragments (Let's say fragment A), I have a vertical recyclerview.
The problem is arises when I want to navigate from Fragment A to other fragments (Again, in the same activity). Suppose that I'm clicking the middle member of the list, then I navigate to the next fragment. When I want to back into the first fragment (Fragment A), it inflates the list from first. Seeming that it looses its position, however, using onSaveInstanceState() and also onCreateView(), I try to store and restore the selected item's position every time I quit or enter the fragment.
I think this issue may be originated from fragments' lifecycle which all are in one single activity. As this answer, mentioning :
In a Fragment, all of their lifecycle callbacks are directly tied to their parent Activity. So onSaveInstanceState gets called on the Fragment when its parent Activity has onSaveInstanceState called.
and that's why I emphasize on the "single" activity. How can we handle such situation?
I have debugged my program and onSaveInstanceState and onCreateView were not even called when they were supposed to. Below is my attempted code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null){
selectedItem = savedInstanceState.getInt(BUNDLE_KEY_SELECTED_ITEM);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null){
selectedItem = savedInstanceState.getInt(BUNDLE_KEY_SELECTED_ITEM);
}
myRecyclerView = (RecyclerView) v.findViewById(R.id.rv_mine);
// mData is a an ArrayList
myAdapter = new MyAdapter(mData, getContext(), this.selectedItem);
myLayoutManager = new LinearLayoutManager(getActivity());
myRecyclerView.setLayoutManager(myLayoutManager);
myRecyclerView.setAdapter(myAdapter);
return v;
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(BUNDLE_KEY_SELECTED_ITEM,selectedItem);
super.onSaveInstanceState(outState);
}
Well you can use .add() to add fragments to your activity than .replace(), Which preserves the state of the fragment within the activity, and if using replace function which will cause the onCreateView callback to trigger again.
I have a Navigation Drawer with 6 Fragments. One of them should contain a ListView. I got it to work and I can switch through all the Fragments via the Navigation Drawer. When I press on the one containing the ListView I can see it, just as it should be. However, when I go to the same Fragment (containing the ListView) while I'm in it already, the ListView isn't displayed anymore. This is probably due to the fact, that I set the ListView on the onActivityCreated() method in the Fragment. I'm curious now though, what happens when I press on the same Fragment again. Does the activity stay loaded and the Fragment needs to be inflated again and through this the OnActivityCreated() method isn't called again? And if so, what do I have to do, that the ListView gets displayed again?
My Fragment.class currently looks like this:
public class FahrplanFragment extends Fragment {
private ListView listView;
private Resources res;
private String[] plans;
private ArrayAdapter<String> adapter;
public FahrplanFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_fahrplan, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listView = (ListView) getActivity().findViewById(R.id.listview_fahrplan);
res = getResources();
plans = res.getStringArray(R.array.plans);
adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1,
android.R.id.text1, plans);
listView.setAdapter(adapter);
}
}
As in the documentation
Called when the fragment's activity has been created and this
fragment's view hierarchy instantiated. It can be used to do final
initialization once these pieces are in place, such as retrieving
views or restoring state. [..] This is called after
onCreateView(LayoutInflater, ViewGroup, Bundle) and before
onViewStateRestored(Bundle).
It is called every time. You can check by logging the event.
Can you post the code of the event handling when click on the navagition drawer button?
EDIT 1
Here I re-created you project to test it and it works. The fragment loads every time with the list view inside. Moreover, it displays the events when they happens and shows clearly that onActivityCreated is called every time the fragment is replaced.
The best suggestion I can give here is to log your code using Log.d() or System.out.println("...") in various steps of the code, or even better enter in debug mode by setting breakpoints and inspect the code while it's running to understand when it enter in some methods and when not.
I have read a lot of things about the data save instance and restore but Unable to implement in my case. What is my case in application .
I am using the Activity (MainActivity) and calling the fragment in it let say ParentFragment
ParentFragment Calls the ChildFragment in it though ParentFragment has its own views such as TextViews which takes First name, Last name and age and below this part I am programatically calling the ChildFragment
So in this way I have 2 fragments in the MainActivity, which is being shown to the user at a time on the screen
**What I want **
I want when User has change the orientation the layout should also change but I also want that the text fields should maintain there data in them .
I want to save some more fragment variables and their data also on configuration changes and retrieve them after the new layout is set after screen orientation changed.
**Problems and Confusions **
I have no idea If i set the Fragment.setRetainInstance(true) then would my fragment still be able to receive the onConfiguration Change call back?
When I rotate my device, the fragment gets re-initialize and also my activity has the Asynctask and that runs again , i want my activity to hold the same data . How can I achieve that?
Please help me and give me some hint.
If you want to change layout on orientation change then,you should not handle configuration change by retaining the activity(by setting android:configChanges="orientation" value for corresponding activity defined in manifest) or by using setRetainInstance() in fragment.
For saving the fragment state on configuration change use
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(FIRST_NAME_VALUE_KEY, firstNameTextView.getText());
outState.putInt(LAST_NAME_VALUE_KEY, lastNameTextView.getText());
super.onSaveInstanceState(outState);
}
and for writing back state to the views in fragment use
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.my_fragment, container, false);
if (savedInstanceState != null) {
String firstName = savedInstanceState.getInt(FIRST_NAME_VALUE_KEY);
String lastName = savedInstanceState.getInt(LAST_NAME_VALUE_KEY);
firstNameTextView.setText(firstName);
lastNameTextView.setText(lastName);
}
return view;
}
You will receive onConfigurationChanged() callback by retaining activity.it is not related to Fragment.setRetainInstance(true).
To avoid running asynctask again,We can store data and retain it using following callbacks inside the activity.
#Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
// Save custom values into the bundle
savedInstanceState.putInt(SOME_VALUE, someIntValue);
savedInstanceState.putString(SOME_OTHER_VALUE, someStringValue);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
in onCreate(Bundle savedInstanceState) call back you can check for savedInstanceSate , based on that you can call u asynctask or retain values as below
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
asyncTask.execute();
} else {
someIntValue = savedInstanceState.getInt(SOME_VALUE);
someStringValue = savedInstanceState.getString(SOME_OTHER_VALUE);
}
}
I have created a fragment where i used recyclerview and recyler items can be select/unselect . But problem is that when i move to other fragment and back to the same fragment then selected data lost .
How can i get the fragment as like previous condition where selected items will be at selected condition .
Try to use SaveInstanceState lets check one sample below
public class MainFragment extends Fragment {
// These variable are destroyed along with Activity
private int someVarA;
private String someVarB;
...
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("someVarA", someVarA);
outState.putString("someVarB", someVarB);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
someVarA = savedInstanceState.getInt("someVarA");
someVarB = savedInstanceState.getString("someVarB");
}
}
There is no onRestoreInstanceState method inside Fragment.
For Fragment, there is some special case that is different from Activity and I think that you need to know about it. Once Fragment is returned from backstack, its View would be destroyed and recreated.