I have RecyclerView that uses GridLayoutManger in Fragment as follows:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//some code
mGridLayoutManager = new GridLayoutManager(getActivity(), 2, GridLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(mGridLayoutManager);
//some code
}
The RecyclerView is populated by adapter that loads data from CursorLoader,my issue is that I am trying to restore saved scroll position of GridLayoutManger to use when device rotates as follows:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mPosition=mGridLayoutManager.findFirstVisibleItemPosition();
outState.putInt(GRID_LAYOUT_SCROLL_KEY,mPosition);
}
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mPosition=savedInstanceState.getInt(GRID_LAYOUT_SCROLL_KEY);
mGridLayoutManager.scrollToPosition(mPosition);
}
}
but no thing changes when rotation happens as though scrollToPosition() method does not work at all.
I tried to put "mGridLayoutManager.scrollToPosition(mPosition);" inside "onLoadFinished" but I still get the same result!
What is the proplem?
Related
I'm trying to understand the process of saving and restoring state using fragments. I've created sliding navigation menu using it.
In one of the fragments there is this code:
public class FifthFragment extends Fragment {
CheckBox cb;
View view;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore save state
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// save state
}
}
For example I want to save the state of the CheckBox before user exits the fragment and restore it when the fragment is created again. How to achieve this?
EDIT:
According to raxellson's answer I've changed my fragment to this:
public class FifthFragment extends Fragment {
private static final String CHECK_BOX_STATE = "string";
CheckBox cb;
View view;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
if (savedInstanceState == null) {
Log.i("statenull", "null");
}
if (savedInstanceState != null) {
// Restore last state for checked position.
boolean checked = savedInstanceState.getBoolean(CHECK_BOX_STATE, false);
cb.setChecked(checked);
}
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(CHECK_BOX_STATE, cb.isChecked());
}
}
I got logged I/statenull: null so savedInstanceState was not saved. What am I doing wrong?
You want to save the value of your current checked state in onSaveInstanceState.
Something like this:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(CHECK_BOX_STATE, cb.getChecked());
}
and then when your view is created you want to get the value if it's present. And set your CheckBox state with it.
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
if (savedInstanceState != null) {
// Restore last state for checked position.
boolean checked = savedInstanceState.getBoolean(CHECK_BOX_STATE, false);
cb.setChecked(checked);
}
return view;
}
EDIT:
When you add the fragment, make sure to add it with a tag or id so that you can retrieve the same instance.
You could do a helper method to retrieve fragment and set the fragment.
private void setFragment(String tag, Fragment newFragment) {
FragmentManager fm = getSupportFragmentManager();
Fragment savedFragment = fm.getFragmentByTag(tag);
fm.replace(R.id.container, savedFragment != null ? savedFragment : newFragment, tag);
fm.commit();
}
so you your switch you can call the helper method instead.
switch (position) {
case 0:
setFragment("A", new FragmentA());
break;
....
}
Note: This is just an example not best practice since you are creating new fragments every time in your switch case now anyways. But it might point you in the right direction.
After see all the example. Here is the solution for save fragment state:
Two steps for this:
1.
String saveValue;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
saveValue = "";
} else {
saveValue = savedInstanceState.getString("saveInstance");
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
//save the values of fragment if destroy on second to back
if (!saveValue.isEmpty())
savedInstanceState.putString("saveInstance", saveValue);
}
In onSaveInstanceState you can save your values. And after destroy fragment you can receive your values through onCreate.
I have a Android.Support.V4.Fragment that contains both a MapView and a RecyclerView. These are separate views in the Fragment.
The app crashes when the device is rotated:
Android.OS.BadParcelableException: ClassNotFoundException when unmarshalling: android.support.v7.widget.RecyclerView$SavedState
I am passing the lifecycle methods to the MapView as required by the docs:
private MapView mapView;
private RecyclerView myRecyclerView;
private RecyclerView.LayoutManager myLayoutManager;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
myRecyclerView = myRootView.FindViewById<RecyclerView>(Resource.Id.myRecyclerView);
myLayoutManager = new LinearLayoutManager(activity, LinearLayoutManager.Horizontal, false);
myRecyclerView.SetLayoutManager(myLayoutManager);
myRecyclerAdapter = ...
myRecyclerView.SetAdapter(myRecyclerAdapter);
mapView.OnCreate(savedInstanceState);
}
public override void OnSaveInstanceState(Bundle outState)
{
...
if (mapView != null) mapView.OnSaveInstanceState(outState);
base.OnSaveInstanceState(outState);
}
If I remove the RecyclerView form the AXML it rotates correctly, if I include it the app crashes at mapView.OnCreate(savedInstanceState) why is this?
For anyone that searches and has the same problem. This is the solution, It would appear to be a 2yr old bug.
I was able to get around it by saving the MapView's saved state on a separate Bundle and then adding it to the outgoing saved state bundle. Then in onCreate(), I would just grab the MapView's saved state from the incoming one and pass it into its onCreate() method, thus stopping the crashes.
In my case I implemented as so:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
myRecyclerView = myRootView.FindViewById<RecyclerView>(Resource.Id.myRecyclerView);
myLayoutManager = new LinearLayoutManager(activity, LinearLayoutManager.Horizontal, false);
myRecyclerView.SetLayoutManager(myLayoutManager);
myRecyclerAdapter = ...
myRecyclerView.SetAdapter(myRecyclerAdapter);
Bundle mapViewSavedInstanceState = savedInstanceState != null ? savedInstanceState.GetBundle("mapViewSaveState") : null;
mapView.OnCreate(mapViewSavedInstanceState);
}
public override void OnSaveInstanceState(Bundle outState)
{
if (mapView != null)
{
Bundle mapViewSaveState = new Bundle(outState);
outState.PutBundle("mapViewSaveState", mapViewSaveState);
mapView.OnSaveInstanceState(outState);
}
base.OnSaveInstanceState(outState);
}
Tallpaul's answer didn't work for me. The solution that he linked was working good though.
public class FragmentMain extends Fragment {
private Bundle savedInstanceState;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
this.savedInstanceState = savedInstanceState;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
containerView = inflater.inflate(R.layout.fragment_main, container, false);
//mapView bug https://code.google.com/p/gmaps-api-issues/issues/detail?id=6237#c9
final Bundle mapViewSavedInstanceState =
savedInstanceState != null ?
savedInstanceState.getBundle("mapViewSaveState") : null;
vmGoogleMap.create( mapViewSavedInstanceState ;
return containerView;
}
#Override
public void onSaveInstanceState(Bundle outState) {
//mapView bug https://code.google.com/p/gmaps-api-issues/issues/detail?id=6237#c9
final Bundle mapViewSaveState = new Bundle(outState);
vmGoogleMap.onSaveInstanceState(mapViewSaveState);
outState.putBundle("mapViewSaveState", mapViewSaveState);
super.onSaveInstanceState(outState);
}
}
I have a MainActivity with a ViewPager. The ViewPager consists of two Fragments each holding a single RecyclerView. I need to have a reference to the RecyclerView for updating/animating it etc. in the Fragment.
A problem occurs on screen rotation, because for some reason I cannot set the member variable back to the RecyclerView.
public abstract class NotifListFragment extends Fragment {
protected RecyclerView mRecyclerView;
public NotifListFragment() {
// Required empty public constructor
EventBusHelper.getBus().register(this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
mRecyclerView = (RecyclerView) inflater.inflate(R.layout.fragment_notif_list, container, false);
setupRecyclerView(mRecyclerView);
return mRecyclerView;
}
So here in the onCreateView I always inflate the fragment and save the reference in mRecyclerView variable. However, when I later try to access it, it is null.
I am using Otto to call both Fragment's onDataChanged() method. It is called when a button in the RecyclerView is pressed.
#Subscribe
public void onDataChanged(DataChangedEvent event) {
List<CustomNotification> oldData = ((NotifRecyclerViewAdapter) mRecyclerView.getAdapter()).getmValues();
List<CustomNotification> newData = getNotifications();
}
The mRecyclerView.getAdapter() is null in this call.
Fragment's getView() also returns null. What is happening?
There are a few things that I found wrong here. Please don't call setupRecyclerView(mRecyclerView); in your onCreateView(), this is something that should be called on your activityCreated() because your fragment might not have been attached to your activity till then. Plus I don't like to do findViewByIds there.
Use the onViewCreated() and onActivityCreated() callbacks.
So your code would look something like this.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_notif_list, container, false);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mRecycler = (RecyclerView) view.findViewById(R.id.yourId);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
setupRecyclerView(mRecycler);
}
It's possible that during your call to onDataChanged() that the fragment might not have been added. Do a null check there.
I am using 4 loaders in my application to load data into a listview. When I change the orientation the loader manager always tries to load the first loader. To solve this issue I am restarting the loader in "onResume" of the application. Now the listview is populated with the data but the position of the list before orientation change in not maintained. Is there any way to maintain the loader state on orientation change ?
If you can't prevent the re-loading of the list, you can at least restore the position:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (null != savedInstanceState) {
mViewPosition = savedInstanceState.getInt(KEY_LIST_CUR_POS);
mViewOffset = savedInstanceState.getInt(KEY_LIST_VIEW_OFFSET);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.status_list, container, false);
return(view);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Restore list position.
getListView().setSelectionFromTop(mViewPosition, mViewOffset);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save current view in list.
outState.putInt(KEY_LIST_CUR_POS, mViewPosition);
outState.putInt(KEY_LIST_VIEW_OFFSET, mViewOffset);
}
#Override
public void onPause() {
super.onPause();
mViewPosition = getListView().getFirstVisiblePosition();
View v = getListView().getChildAt(0);
mViewOffset = (v == null) ? 0 : v.getTop();
}
I'm having an issue with the ViewPager where my ListView is loosing it's scroll position.
The state of the ListView can easily be stored and restored using:
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.frag_cool_things, container, false);
AdvListView listView = (AdvListView) v.findViewById(R.id.lv0);
listView.setOnItemClickListener( mOnListItemClicked );
if (null != savedInstanceState)
{
listView.onRestoreListViewInstanceState(savedInstanceState.getParcelable("list_state"));
}
mListView = listView;
return v;
}
#Override
public void onSaveInstanceState (Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putParcelable("list_state", mListView.onSaveListViewInstanceState());
}
However the problem is that when fragments are being swiped onDestroyView() gets called but never calls onSaveInstanceState (Bundle outState).
Rotating the screen and such restores the ListView state just fine but swiping I can't figure out how to restore the list properly.
Update 12/17/11:
I actually found the correct way to save the content of the Fragments. You must use FragmentStatePagerAdapter. This adapter properly saves the state of the fragment! :)
OLD:
Well I found a way to do this.. Please share your input if you believe this is a huge no no! :P
Here is my FragmentBase class that fixed this issue:
public abstract class FragmentBase extends Fragment
{
private boolean mInstanceAlreadySaved;
private Bundle mSavedOutState;
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
if (null == savedInstanceState && null != mSavedOutState) {
savedInstanceState = mSavedOutState;
}
mInstanceAlreadySaved = false;
return onCreateViewSafe(inflater, container, savedInstanceState);
}
#Override
public void onSaveInstanceState (Bundle outState)
{
super.onSaveInstanceState(outState);
mInstanceAlreadySaved = true;
}
#Override
public void onStop()
{
if (!mInstanceAlreadySaved)
{
mSavedOutState = new Bundle();
onSaveInstanceState( mSavedOutState );
}
super.onStop();
}
public abstract View onCreateViewSafe (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
}