1

I have a screen where the user presses a button to bring up a DialogFragment with an EditText. They enter their information, and press Ok. If they press the button again, I've made it so the EditText will display the information they had just put in, and everything works fine.

However, if they enter the information, press Ok, and then use the options menu to go look at another screen/fragment, the information will not be displayed in the EditText once they press the button again.

How can I save the information even if the user navigates away from that screen for a moment? I think it would require a newInstance method in the fragment class, and then have the hosting activity call newInstance instead of the constructor. But I'm unsure of how to implement it. Any help appreciated. Thanks!

SingleFragmentActivity:

public abstract class SingleFragmentActivity extends FragmentActivity
{
    protected abstract Fragment createFragment();

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);

        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);

        if (fragment == null)
        {
            fragment = createFragment();
            fm.beginTransaction()
                .add(R.id.fragmentContainer, fragment)
                .commit();
        }
    }
}

Relevant code from hosting activity, AdviceActivity:

public class AdviceActivity extends SingleFragmentActivity
{
    @Override
    protected Fragment createFragment()
    {
        return new AdviceFragment();
    }
}

Relevant code from the hosting fragment, AdviceFragment:

private boolean isTextButtonFirstClick = true;

...

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
    mTextButton = (Button) v.findViewById(R.id.textButton);
    mTextButton.setOnClickListener(new View.OnClickListener()
    {
        public void onClick(View v)
        {
            if (isTextButtonFirstClick)
            {
                FragmentManager fm = getActivity().getSupportFragmentManager();
                InputTextFragment dialog = InputTextFragment.newInstance("", isTextButtonFirstClick);
                dialog.setTargetFragment(AdviceFragment.this, REQUEST_TEXT);
                dialog.show(fm, DIALOG_TEXT);
                isTextButtonFirstClick = false;
            }
            else
            {
                FragmentManager fm = getActivity().getSupportFragmentManager();
                InputTextFragment dialog = InputTextFragment.newInstance(mAdvice.getText(), isTextButtonFirstClick);
                dialog.setTargetFragment(AdviceFragment.this, REQUEST_TEXT);
                dialog.show(fm, DIALOG_TEXT);
            }
        }
    });
...
}

Relevant code from DialogFragment InputTextFragment:

public static InputTextFragment newInstance(String text, boolean isTextButtonFirstClick)
{
    Bundle args = new Bundle();
    args.putSerializable(EXTRA_TEXTBUTTON_FIRSTCLICK, isTextButtonFirstClick);
    args.putSerializable(EXTRA_TEXT, text);

    InputTextFragment fragment = new InputTextFragment();
    fragment.setArguments(args);

    return fragment;
}

...

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
    ...

    boolean isTextButtonFirstClick = getArguments().getBoolean(EXTRA_TEXTBUTTON_FIRSTCLICK);

    final EditText editText = (EditText) v.findViewById(R.id.dialogInputEditText);
    if (!isTextButtonFirstClick)
    {
        editText.setText(text);
        editText.setSelection(text.length());
    }

    ...
}

XML activity_fragment:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragmentContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</RelativeLayout>
pez
  • 3,859
  • 12
  • 40
  • 72

1 Answers1

1

It sounds as if the boolean isTextButtonFirstClick simply is mistakenly set to false.

This would make sense of a new instance of Advice Fragment is created during the navigation.

Try

  • Setting setRetainInstance(true) in onCreate of Advice Fragment in order to keep the boolean.

  • If R.id.fragmentContainer is a fragment tag in XML, then change it to a LinearLayout or RelativeLayout (you are adding a fragment on top anyways, and I suspect the findFragmentById to always return null)

  • Change the code in your Activity to:

    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = fm.findFragmentByTag("singleFragment");
    
    if (fragment == null)
    {
        fragment = createFragment();
    }
    
       fm.beginTransaction()
            .replace(R.id.fragmentContainer, fragment,  "singleFragment" )
            .commit();
    

Generally when programmatically adding fragments, you use tags, whereas when defining the fragment in XML letting the Android framework handle the lifecycle, you can find it by id.

Looks to me as if you are in a in-between solution, trying to do both, which will not work as intended.

Hope this can help and sorry in advance if there are formatting issues. The answer has been written from a phone :-)

Edit:

If you only wanted to save simple types of info (such as the boolean), I would point you to one of my old answers here: Saving textview in a fragment when rotating screen

Your last comment revealed that you have some complex information (text, photos, videos etc) that you want to persist.

Making all that Parcelable will be a huge pain, so here goes my second advice:

  1. Add EventBus to your project https://github.com/greenrobot/EventBus
  2. Create a holder class for the information, such as

    public class AdviceHolder {
        private boolean isTextButtonFirstClick;
        private String text;
        private BitMap image;
        ... 
    
       // setter and getter methods (if using eclipse alt+shit+s 'create setter and getters from fields') 
    }
    
  3. Now when starting AdviceActivity, you prepare a new AdviceHolder

    public class AdviceActivity extends SingleFragmentActivity
    {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            // make a new AdviceHolder if none existed
            AdviceHolder holder = new AdviceHolder();
            AdviceHolder existingHolder = EventBus.getDefault().getStickyEvent(AdviceHolder.class);
            if (existingHolder == null) {        
                 EventBus.getDefault().postSticky(holder);
            }
    
            super.onCreate(savedInstanceState);
        }
    
        @Override
        protected Fragment createFragment()
        {
            return new AdviceFragment();
        }
    }
    

This step will make a AdviceHolder object available anywhere in your code. Think of it as a global repository.

This means that no matter how you move between Activites or Fragments, they all have access to AdviceHolder and can edit it.

So for example in you AdviceFragment:

private AdviceHolder holder;

...
@Override
public void onCreate (Bundle savedInstanceState) {
    AdviceHolder holder = (AdviceHolder)EventBus.getDefault().getStickyEvent(AdviceHolder.class);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
    // read isTextButtonFirstClick from holder
    boolean isTextButtonFirstClick = holder.isTextButtonFirstClick();

    mTextButton = (Button) v.findViewById(R.id.textButton);
    mTextButton.setOnClickListener(new View.OnClickListener()
    {
        public void onClick(View v)
        {
            if (isTextButtonFirstClick)
            {
                FragmentManager fm = getActivity().getSupportFragmentManager();
                InputTextFragment dialog = InputTextFragment.newInstance("", isTextButtonFirstClick);
                dialog.setTargetFragment(AdviceFragment.this, REQUEST_TEXT);
                dialog.show(fm, DIALOG_TEXT);

                // update isTextButtonFirstClick in holder
                holder.setTextButtonFirstClick(false);
                EventBus.getDefault().postStickyEvent(holder);
            }
            else
            {
                FragmentManager fm = getActivity().getSupportFragmentManager();
                InputTextFragment dialog = InputTextFragment.newInstance(mAdvice.getText(), isTextButtonFirstClick);
                dialog.setTargetFragment(AdviceFragment.this, REQUEST_TEXT);
                dialog.show(fm, DIALOG_TEXT);
            }
        }
    });
...
}

When you at some point are done filling the AdviceHolder (and sent it to a server or whatever the plan is), remove it from EventBus to enable the creation of a new holder in AdviceActivity.

EventBus.getDefault().removeStickyEvent(AdviceHolder.class);

For other examples about EventBus have a look at http://www.stevenmarkford.com/passing-objects-between-android-activities/

This is all a lot of information, hope it is not too confusing.

Community
  • 1
  • 1
cYrixmorten
  • 7,110
  • 3
  • 25
  • 33
  • Thank you for the reply! When you say to change the code in the `Activity`, do you mean change the code in `AdviceActivity` or `SingleFragmentActivity`? I'm very new to Android so I'm not sure. Other fragment Activities extend `SingleFragmentActivity` as well. – pez Oct 02 '14 at 02:00
  • 1
    No problem :) I meant SingleFragmentActivity. If these steps are not working I would like you to share the `R.layout.activity_fragment` XML. – cYrixmorten Oct 02 '14 at 10:38
  • Thanks. Last night I updated SingleFragmentActivity with your code, but Android Studio displayed an error saying it expected an interface or class, I think. Instead i made AdviceActivity extend FragmentActivity and implemented your code there, and while it has no errors, the problem still occurred. I'll share the XML as soon as I get home from work today and I'll comment again to update you. Thanks again for the help – pez Oct 02 '14 at 14:54
  • 1
    Maybe a useful approach would be to override onSaveInstanceState() and put the Boolean and String into a bundle, then retrieve the information in onCreate()? Another way might be to override onRestoreInstanceState() – pez Oct 02 '14 at 15:07
  • 1
    Regarding your last comment, that usually is a good idea. The problem (I think) is that you are creating a whole new fragment. Then those approaches will not have any knowledge of the saved boolean. – cYrixmorten Oct 02 '14 at 18:58
  • I added the activity_fragment layout, although it's pretty bare. I used a tag in the layout when trying your solution. Each fragment inflates its own layout in onCreateView, though. I think I will have to make my Advice class Parcelable and use that to pass the entire mAdvice object into the Bundle to prepare for photos, videos, etc. in the future – pez Oct 02 '14 at 23:52
  • Looks fine, just wanted to know how it looks. Now I am almost 100% positive that `findFragmentById` would always return null (as there is no fragment in the XML). I have made an edit to the answer with some more suggestions. – cYrixmorten Oct 03 '14 at 08:16
  • Great answer! Thanks for the extra effort – pez Oct 03 '14 at 14:09