Fragment's state is not being retained - android

I am calling fragment's onSaveInstance() method in which I have saved the value of edittext and checbox in bundle. In onCreateView I am setting those values to my edittext and checkbox but when I run the app and write something in edittext and rotate my device it becomes blank! can anyone help?
public class BlankFragment extends Fragment {
public EditText ed;
public CheckBox cb;
public BlankFragment() { }
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_blank, container, false);
ed = v.findViewById(R.id.edit);
cb = v.findViewById(R.id.check);
if(savedInstanceState != null){
ed.setText(savedInstanceState.getString("string"));
cb.setChecked(savedInstanceState.getBoolean("bool"));
}
return v;
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putString("string",ed.getText().toString());
outState.putBoolean("bool",cb.isChecked());
super.onSaveInstanceState(outState);
}
}
I have logged those setText and setChecked lines, it prints correct values in log but then why it is not setting anything on views!

Related

which method when display dialog show in fragment

I opened dialog in a fragment and when come back to fragment, my fragment's view is null Object.
when the dialog is displaying which lifecycle method fragment is calling?
public class MyFragment extends Fragment{
TextView textView;
View view;
FloatingActionButton actionButton;
#Override
public View onCreateView(final LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_read, container, false);
textView = view.findViewById(R.id.txtSaved);
actionButton = view.findViewById(R.id.floatingActionButton);
actionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
DialogSearch search = new DialogSearch();
search.show(getActivity().getSupportFragmentManager(),"MyDialog");
}
});
return view;
}
}
The problem is the dialog is being showned in the onCreateView life cycle, that is why is the Fragment view is null. You have to do it inside the onViewCreated method.
#Override
public View onViewCreated(View view, Bundle savedInstanceState) {
//Work here, the view argument in the method is the view inflated in the onCreateView method
});
Make a small change,
public class MyFragment extends Fragment{
TextView textView;
View view;
FloatingActionButton actionButton;
#Override
public View onCreateView(final LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_read, container, false);
textView = view.findViewById(R.id.txtSaved);
actionButton = view.findViewById(R.id.floatingActionButton);
actionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
DialogSearch search = new DialogSearch();
search.show(getActivity().getSupportFragmentManager(),"MyDialog");
}
});
return view; / Add this line -------
}
}
You need to return view
when the dialog is displaying which lifecycle method fragment is calling?
onPause() of fragment gets called.

Saving and restoring state using fragments

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.

Android - Text not displayed in TextView in spite of (apparently) working setView

I am working on an application which needs to retrieve some informations from the user and I am using Fragments to do that. The idea is to have a ListFragment that displays the name of different Fragments that allow the user to give informations, when user click on one name in the ListFragment, the corresponding fragment is displayed in full screen if we are in portrait mode or next the ListFragment in landscape mode.
As basis I used the code explained and given in the following links:
google fragment and Dynamic Layouts using the Fragment Manager.
I "slightly" modified the code of the links above to have two TextView in the Fragment used to retrieved informations. I am trying to retain states for this Fragment. I tried to set setRetainInstance to true but it does not work so I am trying with onSaveInstanceState.
In onSaveInstanceState I put the content of the TextView in a bundle and in onCreateView if savedInstanceState is not null then I retrieve the strings to be displayed in the TextView. I can see in debug mode that everything (seems to) work, after onSaveInstanceState the Bundle has the strings and in onCreateView the strings are correctly retrieved and pass to the TextView with setView but at the end the TextView remain empty.
Why does that happens? How can that happen?
Below the code of my fragment:
public class DataFragment extends AbstractFragment {
private final String TAG = getClass().getSimpleName();
private View mView;
private TextView mFirstNameTextView = null;
private TextView mMailTextView = null;
Bundle mArgs;
public DataFragment(){
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mView = inflater.inflate(R.layout.data_fragment, container, false);
mFirstNameTextView = (TextView)mView.findViewById(R.id.setting_Firstname);
mMailTextView = (TextView)mView.findViewById(R.id.setting_Mail);
if (savedInstanceState!=null){
String test = savedInstanceState.getString("name");
String retest = savedInstanceState.getString("mail");
mFirstNameTextView.setText(test);
mMailTextView.setText(retest);
}
return mView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onDetach() {
super.onDetach();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String test = mFirstNameTextView.getText().toString();
String testmail = mMailTextView.getText().toString();
outState.putString("name", mFirstNameTextView.getText().toString());
outState.putString("mail", mMailTextView.getText().toString());
}
#Override
public void onPause() {
super.onPause();
}
#Override
public int getShownIndex() {
int ret = getArguments().getInt("index", 0);
return ret;
}
}
To be complete this Fragment inherit from AbstractFragment. AbstractFragment as mentioned by its name is an abstract class that inherit from Fragment. I use this class because I plan to have several fragments and this class helps me to write less code. For completeness below is the code for the AbstractFragment:
public abstract class AbstractFragment extends Fragment {
public AbstractFragment(){}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setRetainInstance(true);
}
public int getShownIndex(){
int ret = getArguments().getInt("index", 0);
return ret;
}
}
Edit:
Just to be clear, the problem is not to retrieve data, the problem is to display data. I am retrieving the values I stored using onSaveInstanceState but these values are not displayed in TextView. Below a screenshot of my application with a breakpoint on onSaveInstanceState, this screenshot has been taken after I entre the Strings "foo" and "bar" in my TextView and then I rotate the AVD, we can see on the screenshot that strings "foo" and "bar" are inside the savedInstanceState Bundle and the variables test and retest are correctly initialized to "foo" and "bar", but these strings are not displayed in the TextView
You are confusing arguments and savedInstanceState. Go through the developer documents here to know what is savedInstanceState.
Refer arguments bundle rather than savedInstanceState bundle to make it work proper. Refer the following code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mView = inflater.inflate(R.layout.data_fragment, container, false);
mFirstNameTextView = (TextView)mView.findViewById(R.id.setting_Firstname);
mMailTextView = (TextView)mView.findViewById(R.id.setting_Mail);
Bundle arguments = getArguments();
if (arguments !=null){
String test = arguments.getString("name");
String retest = arguments.getString("mail");
mFirstNameTextView.setText(test);
mMailTextView.setText(retest);
}
else {
Log.e(DataFragment.class, "Arguments was null, did not load the name and email TextView");
}
return mView;
}

Maintain fragment's view state

I have seen Link1 for this issue but could understand it right. I have a fragment that loads a list. When i click the list item it opens another activity. But i press back button it loads the list again. I want it to be at the same scroll position where it was before. In above mentioned link it specifies to use flag but i haven't got the point.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dashboard);
android.app.Fragment fragment = new MeFragment();
getFragmentManager().beginTransaction().replace(R.id.layout_FragmentsContainer, fragment).addToBackStack(null).commit();
}
}
public class MeFragment extends Fragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_me, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
meLV = (ListView) getView().findViewById(R.id.lv_Inbox);
loadingListProgress = (ProgressBar) getView().findViewById(R.id.progress_LoadingList);
meList = new ArrayList<Message>();
meAdapter = new MessagesListAdapter(getActivity(), meList);
//addFooter();
meLV.setAdapter(meAdapter);
meLV.setOnItemClickListener(this);
pageCount = 0;
loadmoreProgressDialog = new ProgressDialog(getActivity());
loadmoreProgressDialog.setTitle("Please wait ...");
loadmoreProgressDialog.setMessage("Loading more ...");
loadmoreProgressDialog.setCancelable(true);
loadUserMessages();
meLV.setOnScrollListener(new EndlessScrollListener() {
#Override
public void onLoadMore(int page, int totalItemsCount) {
// TODO Auto-generated method stub
//addFooter();
loadmoreProgressDialog.show();
loadUserMessages();
}
});
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// TODO Auto-generated method stub
Utils.showToast_msg(getActivity(), "MessageItemClicked");
ReferralDetailFragment fragment = new ReferralDetailFragment();
getFragmentManager().beginTransaction().replace(R.id.layout_FragmentsContainer, fragment).addToBackStack(null).commit();
}
}
public class ReferralDetailFragment extends Fragment implements OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_referraldetail,container, false);
linkToAcknowledge = (TextView) view.findViewById(R.id.lbl_Link_to_Acknowledge);
return view;
}
}
I implemented a simple solution for this in my app, basically when you press back to go to the fragment again, onCreateView() is called. Here in onCreateView() you have done all initialization, so we change
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_me, container, false);
/*
*Whatever you want to do
*
*/
return view;
}
to:
View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
if(view==null){
view = inflater.inflate(R.layout.fragment_me, container, false);
/*
*Whatever you want to do
*
*/
}
else{
((ViewGroup)view.getParent()).removeView(view);
}
return view;
}
Here, we move View view outside and make it a class variable. So if it is the first time the fragment is called, it is null and the initialization occurs, otherwise it goes to else black. Else block is required because onCreateView() adds whatever it returns as a child of the view's parent, so since view is already there, we remove it and onCreateView automatically adds it again.
According to our exchange in the comments, I completely deletde my answer and re-write a new one.
I copy/paste the code from one of my apps and removing the useless things and changing the names. Hope there is not too many typing mistakes, at that it is the minimum required to have it working.
When I pop back to FirstFragment from SecondFragment, the scroll position of FirstFragment is the same as when I clicked an item to load the SecondFragment.
Note that I don't extend FragmentActivity. I have an activity which loads the fragments.
Extend/modify to match your needs.
MainActivity :
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
}
}
FirstFragment Class :
public class FirstFragment extends Fragment implements OnItemClickListener {
private ListView mListView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.first_fragment_layout, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mListView = (ListView) getView().findViewById(R.id.listview_first_fragment);
mListView.setAdapter(mAdapter); // depends on your adapter
mListView.setOnItemClickListener(this);
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mListView.setItemChecked(position, true);
//in case you need, set the bundle here, for example pass the position
Bundle arguments = new Bundle();
arguments.putInt("position", position);
SecondFragment secondFragment = new SecondFragment();
secondFragment.setArguments(arguments);
getFragmentManager().beginTransaction().replace(R.id.fragment_container, secondFragment).addToBackStack(null).commit();
}
}
SecondFragment Class :
public class SecondFragment extends Fragment {
private Integer mPosition;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.second_fragment_layout, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle arguments = getArguments();
if (arguments == null) {
mPosition= 0;
} else {
mPosition= arguments.getInt("Position");
}
}
}
What you are trying to achieve may be done with help of savedInstanceState. i also had this kind of problem which i resolved by using add() method instead of replace() in transition.
If you can change your method or already not using add() than give it a shot.
and if add() method didn't do the trick then check the implementation of savedInstanceState.
correctly save instance state.
How to save states of fragment views.

Fragment doesn't call onSaveInstanceState when swiping ViewPager?

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);
}

Categories

Resources