Saving the state of fragments? - android

I have a fragment class. Here is it below:
public class FragmentA extends Fragment {
Button button;
WebView myWebView;
int mCurCheckPosition;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved)
{
View mainView = (View) inflater.inflate(R.layout.frag_a, group, false);
myWebView = (WebView) mainView.findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());
myWebView.getSettings().setPluginsEnabled(true);
myWebView.getSettings().setBuiltInZoomControls(false);
myWebView.getSettings().setSupportZoom(false);
myWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
myWebView.getSettings().setAllowFileAccess(true);
myWebView.getSettings().setDomStorageEnabled(true);
myWebView.loadUrl("http://www.bbc.co.uk");
return mainView;
}
public class MyWebViewClient extends WebViewClient {
/* (non-Java doc)
* #see android.webkit.WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String)
*/
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.endsWith(".mp4"))
{
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), "video/*");
view.getContext().startActivity(intent);
return true;
}
else {
return super.shouldOverrideUrlLoading(view, url);
}
}
}
}
The problem is when I move to and from another fragment, the state of the original fragment (what web page it was on) is lost.
How can I prevent this? I want the state of the web page to remain switching to and from each fragment.
Thanks

You should use the WebView.saveState(Bundle state) method to in your onSaveInstanceState(Bundle outState) method and then restore the state using WebView.restoreState(Bundle state) in your onActivityCreated(Bundle savedInstanceState) method
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mWebView.saveState(outState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mWebView.restoreState(savedInstanceState);
}
Also keep in mind the Fragment Lifecycle. If your unsure where to restore the state (onCreate, onCreateView, onActivityCreated), take a look at the Fragment Lifecycle documentation to figure out the right place.
http://developer.android.com/guide/components/fragments.html
onCreate()
The system calls this when creating the fragment. Within your implementation, you should initialize essential components of the fragment that you want to retain when the fragment is paused or stopped, then resumed.
onCreateView()
The system calls this when it's time for the fragment to draw its user interface for the first time. To draw a UI for your fragment, you must return a View from this method that is the root of your fragment's layout. You can return null if the fragment does not provide a UI.
onActivityCreated()
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. It is also useful for fragments that use setRetainInstance(boolean) to retain their instance, as this callback tells the fragment when it is fully associated with the new activity instance. This is called after onCreateView(LayoutInflater, ViewGroup, Bundle) and before onStart().

I was having the same problem with webView content in a fragment. What worked for me was hiding the current fragment when loading a new one instead of replacing it. This causes the view to remain in the previous fragment and not be destroyed. View code below.
protected void showTableFragment( Fragment fragment, boolean allowBack, Asset table ) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (table != null)
tableSection = (SectionTable) table;
Fragment lastFragment = fragmentManager.findFragmentByTag("first_frag" );
if ( lastFragment != null ) {
transaction.hide( lastFragment );
}
if ( fragment.isAdded() ) {
transaction.show( fragment );
}
else {
transaction.add( R.id.fragment, fragment, "second_frag" ).setBreadCrumbShortTitle( "second_frag" );
}
if ( allowBack ) {
transaction.addToBackStack( "second_frag" );
}
transaction.commit();
}

Related

Recyclerview in fragment not loading after screen rotate

I want to save my fragment with recycler view, to restore it whlie screen rotate. I was looking for instructions in the net and I get:
In my Fragment
private Parcelable recyclerViewState;
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
list.getLayoutManager().onRestoreInstanceState(recyclerViewState);
}
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
recyclerViewState = layoutManager.onSaveInstanceState();
}
in my Activity
//onCreate
if (savedInstanceState != null) {
getSupportFragmentManager().getFragment(savedInstanceState, LIST_USERS_FRAGMENT);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().getFragment(outState, LIST_USERS_FRAGMENT);
}
I was debbuging and it looks fine, however while I rotate my screen I get blank fragment, with no recycler view. Could You help me investigate why it's happening ?
The default behavior in Android is destroying all the attached fragments on configuration changes. To bypass fragment recreation you should use setRetainInstance(true) in the OnCreate callback of the fragment.
There are a different options to handle the orientation changes:
Lock screen orientation
<activity
android:name="com.example.test.activity.MainActivity"
android:screenOrientation="portrait"/>
Prevent Activity to recreated
<activity
android:name="com.example.test.activity.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"/>
Save basic state
public class MainActivity extends Activity{
private static final String SELECTED_ITEM_POSITION = "ItemPosition";
private int mPosition;
#Override
protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
// Save the state of item position
outState.putInt(SELECTED_ITEM_POSITION, mPosition);
}
#Override
protected void onRestoreInstanceState(final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Read the state of item position
mPosition = savedInstanceState.gettInt(SELECTED_ITEM_POSITION);
}
}
Save complex objects
Override
onRetainNonConfigurationInstance()
getLastNonConfigurationInstance()
After API level 13 these methods have been deprecated in favor of the more Fragment’s setRetainInstance(boolean) capability, which provides a much cleaner and modular means of retaining objects during configuration changes.

cannot restore view pager fragment state

i have a view pager with 3 fragments. i use retrofit rest api to populate each of my fragments for the first time. What i would like to achieve is when the user swipes back either in first or third fragment(those 2 fragment are being being destroyed by the view pager) to restore the data(saved in an array list) and not make a rest api call again. What i have done is to save the array list of downloaded data in onSaveInstanceState() and successfully retrieve it when the user swipes back to one of the 2 above fragments only FOR THE 1 FIRST TIME. The problem is when i navigate back to either one of the 2 fragments the specific bundle key where the array list was saved contains null value.
CompletedSurveysFragment(the third fragment):
public class CompletedSurveysFragment extends Fragment implements SAMVCView {
private static final String debugTag = CompletedSurveysFragment.class.getSimpleName();
private View view;
private RecyclerView completedSurveysRcV;
private SAMVCPresenterImpl SAMVCpresenterImpl;
private SurveysRcvAdapter surveysRcvAdapter;
private List<SurveyData> data;
public CompletedSurveysFragment() {}
public static CompletedSurveysFragment newInstance() {
return new CompletedSurveysFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.e(debugTag, "onCreateView");
if ( view == null ) view = inflater.inflate(R.layout.fragment_completedsurveys, container, false);
completedSurveysRcV = (RecyclerView) view.findViewById(R.id.completedSurveysRcV);
return view;
}
// TODO: 21/6/2016 configure Limit and offset values
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.e(debugTag, "onActivityCreated " + savedInstanceState);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
completedSurveysRcV.setHasFixedSize(true);
completedSurveysRcV.setLayoutManager(linearLayoutManager);
completedSurveysRcV.addItemDecoration(new DividerItemDecoration(ContextCompat.getDrawable(getActivity(), R.drawable.divider)));
if (savedInstanceState == null) {
SAMVCpresenterImpl = new SAMVCPresenterImpl(this);
SAMVCpresenterImpl.getSurveysBasedOnSpecificFirmId(new AllSurveysBody(getResources().getString(R.string.list_surveys), LoginActivity.getSessionPrefs(getActivity()).getInt(getResources().getString(R.string.firm_id), 0), getResources().getString(R.string.completed), 8, 0));
surveysRcvAdapter = new SurveysRcvAdapter(null, completedSurveysRcV);
completedSurveysRcV.setAdapter(surveysRcvAdapter);
} else {
//Log.e(debugTag, savedInstanceState.+"");
if (savedInstanceState.getParcelableArrayList("data") != null) {
Log.e(debugTag, "here "+ savedInstanceState);
surveysRcvAdapter = new SurveysRcvAdapter(savedInstanceState.<SurveyData>getParcelableArrayList("data"), completedSurveysRcV);
completedSurveysRcV.setAdapter(surveysRcvAdapter);
surveysRcvAdapter.notifyDataSetChanged();
}
//Log.e(debugTag, getArguments().getParcelableArrayList("data")+"");
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("data", (ArrayList<? extends Parcelable>) this.data);
Log.e(debugTag, "CompletedFragment onSaveInstanceState "+ outState);
}
#Override
public void onSuccessSurveysFetched(List<SurveyData> data) {
this.data = data;
surveysRcvAdapter = new SurveysRcvAdapter(data, completedSurveysRcV);
completedSurveysRcV.setAdapter(surveysRcvAdapter);
surveysRcvAdapter.notifyDataSetChanged();
}
#Override
public void onFailure() {
}
}
View pager adapter:
public class SurveysPagerAdapter extends FragmentStatePagerAdapter {
private static final String debugTag = SurveysPagerAdapter.class.getSimpleName();
private List<SurveyData> data;
String[] tabText;
public SurveysPagerAdapter(FragmentManager fragmentManager, String[] tabText) {
super(fragmentManager);
this.tabText = tabText;
}
#Override
public Fragment getItem(int position) {
Log.e("SurveysPagerAdapter", position+"");
switch (position) {
case 0:
return CompletedSurveysFragment.newInstance();
case 1:
return OngoingSurveysFragment.newInstance();
case 2:
return PendingSurveysFragment.newInstance();
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
return tabText[position];
}
#Override
public int getCount() {
return 3;
}
}
Your issue is that you're always returning a new instance of your fragment. Instead of calling CompletedSurveysFragment.newInstance(); (and your other Fragments) every time user swipes, create an array of fragments and retrieve it this way:
...
Fragment [] pages = new Fragment[getCount()];
...
#Override
public Fragment getItem(int position) {
Log.e("SurveysPagerAdapter", position+"");
switch (position) {
case 0:
if(pages[position] == null)
pages[position] = CompletedSurveysFragment.newInstance();
return pages[position];
...
}
Then, you can fetch and cache your data in onCreate() and retrieve it later in onResume() in your respective fragments.
I think onPause() and onResume() won't work. Fragments are tied to their parent activity so since you browse through fragments parent activity is on an onResume() status. What I suggest you to do is to use singleton objects to fetch and store your data. Each fragment can keep a reference to the respective object and have instant access to the data you need. There is plenty of info about singleton pattern and how it works.
The reason this is happening is because onSaveInstanceState only occurs when the parent activity is closed. Since the parent activity of your viewpager is still active when you switch between your fragments, onSaveInstanceState will not be called.
To get the data to save like you are intending, what I would likely do instead is save the data in onPause() and retrieve the data in onResume().
EDIT
According to the docs you need to put the call to super.onSaveInstanceState(savedInstanceState); after you modify the Bundle.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}

Saving Instance of Fragment's ListView

I have a Main Activity which contains two Fragments (Frag A and Frag B).
Only one Fragment is to be shown at a time.
Both Fragments are contain a ListView (Common for both Frag A and Frag B).
1.When Activity is created Frag A loads all its values through an AsyncTask
2.When I push a button I switch to Frag B which does the same process
3.Again on push of a button I Switch to Frag A and the AsyncTask is called again
I don't want to carry out the third step .Instead I just want to save the instance of Frag A. So the contents are not loaded again.How can I do it?
I know i will have to stop calling AsyncTask again which will be taken care of. Any help with saving of Instance State is much appreciated.
public class DepositFragement extends Fragment {
ProgressDialog pDialog;
private String TAG="DEPOSITS FRAGMENT";
PrefManager pref;
ListView depList;
ListView depoList;
Boolean isFirstTime=true;
Parcelable depoListInstance;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
return inflater.inflate(R.layout.deposits_fragment,container,false);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("Depo Instance", depoList.onSaveInstanceState());
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(savedInstanceState!=null)
{
depoListInstance = savedInstanceState.getParcelable("Depo Instance");
depoList.onRestoreInstanceState(depoListInstance);
}
depoList=(ListView)getActivity().findViewById(R.id.lvDepositList);
if(isNetworkAvailable())
{
if(isFirstTime) {
new LoadDeposits().execute();
isFirstTime = false;
}
}
else {
AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).create();
alertDialog.setTitle("Info");
alertDialog.setMessage("Internet not available, Cross check your internet connectivity and try again");
alertDialog.setIcon(android.R.drawable.ic_dialog_alert);
alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
alertDialog.show();
}
}
}
You can create a flag on the fragment - like isLoaded that is set to true when the AsyncTask returns with the data.
Then, in the onCreate method of the fragment, you can use:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
So that the data is preserved when the fragment is paused.
In the post execute callback of your async task, set a field to the data returned.
#Override
protected void onPostExecute(List<Object> result) {
DepositFragement.this.dataResult = result;
refreshAdapter();
}
In the onResume:
#Override
public void onResume() {
super.onResume();
if(this.dataResult != null) {
refreshAdapter();
}
}
void refreshAdapter() {
adapter.data = dataResult;
adapter.notifyDataSetChanged();
}
For each fragment you can override the onSaveInstanceState method and save whatever you want in there.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment's state here
}
also you should retrieve it by overriding onActivityCreated.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
if (savedInstanceState != null) {
//Restore the fragment's state here
}
}

What is the best way to save state of RecyclerView?

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.

Why is FragmentPagerAdapter saveState always null?

I want to save the state of my FragmentPagerAdapter so I can reuse it after the orientation of the screen changed. Unfortunately the method saveState returns always null.
My Adapter consists of an ActionBar with two Fragments.
Here ist my method call
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(FRAGMENT, viewPager.getCurrentItem() + 1);
outState.putParcelable(ADAPTER, fragmentTabAdapter.saveState());
super.onSaveInstanceState(outState);
}
But the Adapter in outState is always null. I don't understand why. Is there something special I missed about the use of saveState?
Hope you can help me!
this example works if the fragment is not destroyed
private int pageID;
#Override
public void onPause() {
super.onPause();
pageID = mViewPager.getCurrentItem(); // 1 save
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mViewPager.setCurrentItem(pageID, false); // 2 load
}

Categories

Resources