0

I have a single activity with a navigation drawer (the basic one provided by Eclipse new app wizard). I have a FrameLayout as a container for the different fragments of the app, which are replaced when selecting an item in the navigation drawer. They are also added to the BackStack.

These fragments contain a LinearLayout, which has some EditTexts and a Button. If the button is pressed, a new LinearLayout is created and a couple TextViews are added to it with the content of the EditTexts. The user can repeat this option more than once, so I cannot tell how many LinearLayouts I'll need, therefore I need to add them programmatically.

One of these fragments xml:

<LinearLayout
    android:id="@+id/pen_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/new_pen_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:background="@drawable/border"
        android:orientation="vertical"
        android:paddingBottom="@dimen/home_section_margin_bottom"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/home_section_margin_top" >

        <EditText
            android:id="@+id/new_pen_round"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="number"
            android:hint="@string/new_pen_round_hint"
            android:textSize="@dimen/normal_text_size" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:weightSum="2" >

            <Button
                android:id="@+id/new_pen_cancel_button"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginRight="@dimen/new_item_button_margin_right"
                android:layout_weight="1"
                android:background="@drawable/button_bg"
                android:paddingBottom="@dimen/new_item_button_padding_bottom"
                android:paddingTop="@dimen/new_item_button_padding_top"
                android:text="@string/new_item_cancel_button"
                android:textSize="@dimen/normal_text_size" />

            <Button
                android:id="@+id/new_pen_insert_button"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/new_item_button_margin_left"
                android:layout_weight="1"
                android:background="@drawable/button_bg"
                android:paddingBottom="@dimen/new_item_button_padding_bottom"
                android:paddingTop="@dimen/new_item_button_padding_top"
                android:text="@string/new_pen_insert_button"
                android:textSize="@dimen/normal_text_size" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

There are actually many other EditTexts but I removed them here to keep it short, the result is the same. It's java file:

public class PenaltiesFragment extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_penalties, container, false);
        Button insertNewPen = (Button) view.findViewById(R.id.new_pen_insert_button);
        insertNewPen.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {               
                TextView round = (TextView) getActivity().findViewById(R.id.new_pen_round);
                LinearLayout layout = (LinearLayout) getActivity().findViewById(R.id.pen_layout);
                int numChilds = layout.getChildCount();
                CustomPenaltyLayout penalty = new CustomPenaltyLayout(getActivity(), round.getText()); 
                layout.addView(penalty, numChilds - 1);
            }
        });

        return view;
    }
}

I removed some useless methods, which are just the default ones. CustomPenaltyLayoutis a subclass of LinearLayout which I created, it just creates some TextViews and adds them to itself.

Everything works fine here. The user inserts data in the EditText, presses the Insert button and a new layout is created and added in the fragment.

What I want to achieve is: say that I open the navigation drawer and select another page, the fragment gets replaced and if I go back to this fragment (via navigation drawer or via Back button) I want the text, that the user added, to be still there.

I do not call PenaltiesFragment.newInstance() everytime I switch back to this fragment, I instead create the PenaltiesFragment object once and keep using that one. This is what I do:

Fragment fragment;
switch (newContent) {
// various cases
case PEN:
    if(penFragment == null) // penFragment is a private field of the Main Activity
        penFragment = PenaltiesFragment.newInstance();
    fragment = penFragment;
    break;
}
getSupportFragmentManager()
    .beginTransaction()
    .replace(R.id.container, fragment)
    .addToBackStack("fragment back")
    .commit();

I understand that onCreateView() is called again when the fragment is reloaded, right? So that is probably why a new, blank fragment is what I see. But how do I get the inserted CustomPenaltyLayout back? I cannot create it in the onCreateView() method.

Piero Nicolli
  • 196
  • 1
  • 5
  • Is subclassing LinearLayout absolutely needed? Anyway, if you add some Views programmatically, their state won't be retained by the system if you don't provide a unique id for each added view. Here's a relevant tutorial: http://android-er.blogspot.it/2012/06/programmatically-create-layout-and-view.html – XDnl Sep 25 '14 at 07:56
  • It is not needed, but useful in my opinion, because I have to add many of them and I want them to have the same margins, background, padding, plus I might need to add one or two buttons inside, which send the Layout content to a server and other stuff. Ok, I'll try to add an ID, but what if I don't know how many IDs I will need? – Piero Nicolli Sep 25 '14 at 08:23
  • Why don't just inflate a normal XML file then? Regarding multiple ids, see the second answer to this question: http://stackoverflow.com/questions/1714297/android-view-setidint-id-programmatically-how-to-avoid-id-conflicts. However, I would first try with a fixed number of views to check if the problem is actually solved. – XDnl Sep 25 '14 at 08:33
  • Yeah, that's what I did now. I created the ids.xml file with an id ``, then set my programmatically added layout id with `setId(R.id.myid)`. I added one layout, I switched to another fragment, then pressed back, but my added layout wasn't there. Do I also need to set an id for objects inside the LinearLayout? I noticed that the content of the EditTexts correctly remains. Could my problem be that `onDestroyView()` is called? – Piero Nicolli Sep 25 '14 at 09:24
  • I think you need to set an id for _every_ programmatically added view/layout. – XDnl Sep 25 '14 at 09:31
  • Tried now, but it still doesn't work. – Piero Nicolli Sep 25 '14 at 09:45

1 Answers1

0

I found a solution to my problem. I replaced the default FrameLayout that Android automatically created as a container for my fragments, with a ViewPager, then created a FragmentPagerAdapter like this:

public static class MyAdapter extends FragmentPagerAdapter {

    public MyAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        Fragment fragment;
        switch (position) {
        // ...other cases
        case PEN:
            fragment = PenaltiesFragment.newInstance();
            break;
        // ...other cases
        }
        return fragment;
    }

    @Override
    public int getCount() {
        return 6;
    }
}

Then the only thing left to do to keep all the views at all times has been to add this line to my activity onCreate method.

mPager.setOffscreenPageLimit(5);

See the documentation for details on how this method works.

This way, though, I had to reimplement all the back button logic, but it's still simple, and this is how I did it: I create a java.util.Stack<Integer> object, add fragment numbers to it (except when you use the back button, see below), and override onBackPressed() to make it pop the last viewed fragment instead of using the back stack, when my history stack is not empty.

You want to avoid pushing elements on the Stack when you press the back button, otherwise you will get stuck between two fragments if you keep using the back button, instead of eventually exiting.

My code:

MyAdapter mAdapter;
ViewPager mPager;
Stack<Integer> pageHistory;
int currentPage;
boolean saveToHistory;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mAdapter = new MyAdapter(getSupportFragmentManager());
    mPager = (ViewPager)findViewById(R.id.container);
    mPager.setAdapter(mAdapter);
    mPager.setOffscreenPageLimit(5);

    pageHistory = new Stack<Integer>();
    mPager.setOnPageChangeListener(new OnPageChangeListener() {

        @Override
        public void onPageSelected(int arg0) {
            if(saveToHistory)
                pageHistory.push(Integer.valueOf(currentPage));
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
    saveToHistory = true;
}

@Override
public void onBackPressed() {
    if(pageHistory.empty())
        super.onBackPressed();
    else {
        saveToHistory = false;
        mPager.setCurrentItem(pageHistory.pop().intValue());
        saveToHistory = true;
    }
};
Piero Nicolli
  • 196
  • 1
  • 5