1

I have an app that uses a composite view to show multiple similar type views in a fragment. One custom part of these composite views is the text set for a CheckBox in each composite view. The first time the fragment's lifecycle displays the content for the view all is fine (each checkbox has the correct text as specified in the fragment's xml using attributes), but on each subsequent view (after orientation change or navigating via tabs) all the CheckBox texts are set to the same value (the last composite view's checkbox text value).

My composite view's xml layout info. composite_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">  
  <CheckBox
    android:id="@+id/myCheckBox" 
    style="@style/CheckBoxLeftAligned" 
    android:checked="false"
  />
  ...
</merge>

My attributes file to allow for specific text to be set for each composite view's checkbox. attributes.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="ZZZ">
    <attr name="name"   format="string" />              
  </declare-styleable>
</resources>

My fragment's layout which contains four of my composite views. fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:ctd="http://schemas.android.com/apk/res/com.xxx.yyy"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">
  <RelativeLayout 
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.xxx.yyy.ui.zzz
      android:id="@+id/aaa"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      ctd:name="@string/aaa"
    />
    <com.xxx.yyy.ui.zzz
      android:id="@+id/bbb"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      ctd:name="@string/bbb"
    />
    <com.xxx.yyy.ui.zzz
      android:id="@+id/ccc"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      ctd:name="@string/ccc"
    />
    <com.xxx.yyy.ui.zzz
      android:id="@+id/ddd"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      ctd:name="@string/ddd"
    />
  </RelativeLayout>
</ScrollView>

My composite view's java implementation. zzz.java

package com.xxx.yyy.ui;

... 

public class zzz extends RelativeLayout {

  private static final String TAG = zzz.class.getSimpleName();

  private final Context mContext;

  private CheckBox mLabelCheckBox;


  public zzz(Context context, AttributeSet attrs) {
    super(context, attrs);

    mContext = context;

    initializeLayoutBasics(context);
    processAttributeInformation(context, attrs);


  }

  private void initializeLayoutBasics(Context context) {
    final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    inflater.inflate(R.layout.composite_view, this);    
  }

  private void processAttributeInformation(Context context, AttributeSet attr) {
    mLabelCheckBox = (CheckBox) findViewById(R.id.myCheckBox);

    final TypedArray a = context.obtainStyledAttributes(attr, R.styleable.ZZZ);
    String text = a.getString(R.styleable.ZZZ_name);
    mLabelCheckBox.setText(text);
    Log.d(TAG, "CheckBox=" + mLabelCheckBox.toString() + ", Title= " + payName);            

    a.recycle();
  }

  ...
}

CatLog output

03-10 08:20:31.151: D/MyFragment(400): MyFragment.onAttach
03-10 08:20:31.151: D/MyFragment(400): MyFragment.onCreate
03-10 08:20:31.151: D/MyFragment(400): MyFragment.onCreateView
03-10 08:20:31.200: D/zzz(400): CheckBox=android.widget.CheckBox@40a78e00, Title= MSP
03-10 08:20:31.220: D/zzz(400): CheckBox=android.widget.CheckBox@40a9da58, Title= ISP
03-10 08:20:31.239: D/zzz(400): CheckBox=android.widget.CheckBox@40aa1248, Title= ASP
03-10 08:20:31.250: D/zzz(400): CheckBox=android.widget.CheckBox@40aa4a88, Title= Board Certified Pay
03-10 08:20:31.260: D/zzz(400): CheckBox=android.widget.CheckBox@40aa8310, Title= HPO Board Cert Pay
03-10 08:20:31.270: D/MyFragment(400): MyFragment.onStart
03-10 08:20:31.270: D/MyFragment(400): MyFragment.onResume
03-10 08:20:32.410: D/App(400): onTabUnselected detaching fragment MyFragment
03-10 08:20:32.426: D/App(400): onTabSelected adding fragment OtherFragment
03-10 08:20:32.440: D/MyFragment(400): MyFragment.onPause
03-10 08:20:32.450: D/TotalPayFragment(400): OtherFragment.onCreateView
03-10 08:20:32.519: D/TotalPayFragment(400): OtherFragment.onStart
03-10 08:20:33.120: D/App(400): onTabUnselected detaching fragment OtherFragment
03-10 08:20:33.120: D/App(400): onTabSelected attaching fragment MyFragment
03-10 08:20:33.170: D/MyFragment(400): MyFragment.onCreateView
03-10 08:20:33.190: D/SimplePay(400): CheckBox=android.widget.CheckBox@40acb618, Title= MSP
03-10 08:20:33.201: D/SimplePay(400): CheckBox=android.widget.CheckBox@40ace530, Title= ISP
03-10 08:20:33.210: D/SimplePay(400): CheckBox=android.widget.CheckBox@40ad1c20, Title= ASP
03-10 08:20:33.230: D/SimplePay(400): CheckBox=android.widget.CheckBox@40ad5310, Title= Board Certified Pay
03-10 08:20:33.280: D/SimplePay(400): CheckBox=android.widget.CheckBox@40ad8ad8, Title= HPO Board Cert Pay
03-10 08:20:33.290: D/MyFragment(400): MyFragment.onStart
03-10 08:20:33.290: D/MyFragment(400): MyFragment.onResume

So even my catlog output shows that each of my checkboxes is being set to the specific text that I have set in my fragment's xlm using attributes. But after the second time my fragment goes through onCreateView all of the CheckBox text values are set to "HPO Board Cert Pay". This output is using a Honeycomb emulator.

I have used this same pattern when setting the text in TextBoxes in a composite view, so I am betting this has something to do with Checkboxes.

Any thoughts?

Mike
  • 777
  • 3
  • 7
  • 14
  • Read this http://stackoverflow.com/questions/3542333/how-to-prevent-custom-views-from-losing-state-across-screen-orientation-changes/3542895#3542895 . – user Mar 10 '13 at 15:11
  • I assumed that no special state saving processing was needed since all of my view widgets have ids. This was not needed in my app that using this same pattern where the attribue processing is setting the text for TextViews and not CheckBoxes. Will give this a look. I have a temp work around that exposes a setCheckBoxText method in my zzz class, and then I call that method in my Fragment's onResume. But that is an ugly/fragile pattern. – Mike Mar 10 '13 at 15:22
  • Adding Kobor42's solution from the above link (http://stackoverflow.com/questions/3542333/how-to-prevent-custom-views-from-losing-state-across-screen-orientation-changes/3542895#3542895) did not correct issue. – Mike Mar 10 '13 at 15:30

2 Answers2

0

By default, Android restores the state of all View objects in the activity. To do this properly, each view needs a unique ID (see Recreating an Activity).

The nested structure of your view causes there to be four views with ID aaa, and likewise for other views. This causes the state of the views to be restored incorrectly.

A workaround is to override onSaveInstanceState() and have it clear the outState bundle. That will prevent Android from saving the state of the views. The downside is that you become responsible for preserving the state yourself; otherwise it will be lost, such as a user losing text in an EditText after rotating the screen.

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • Edward, Thanks for the post. Not sure exactly how to interpret your comment. My composite UI element extends RelativeLayout so it does not have an onCreateView or onStart method or have the ability to override those (to my knowledge). In my fragment it only has access to the composite widget and not directly to the checkbox text (desired design). – Mike Jan 14 '14 at 01:37
  • I discovered the cause of the problem and a workaround and updated my answer accordingly. – Edward Brey Nov 19 '14 at 07:06
0

While I cannot answer why Android is behaving as I noted, below is my work around:

I got around this by creating and calling the below function in my onResume and adding a forceUpdateOfCheckBoxText method to my composite UI.

  //Used to force checkboxes to be updated
  private void forceUpdateOfCheckboxesInCompositeViews(ViewGroup parent) {    
    for(int i = 0; i < parent.getChildCount(); i++) {
          View child = parent.getChildAt(i);            

          if(child instanceof ViewGroup) {
            Class<?> c = child.getClass();
            if (c == SimplePay.class) {
              ((SimplePay) child).forceUpdateOfCheckBoxText();
            } 
            else {
                forceUpdateOfCheckboxesInCompositeViews((ViewGroup)child);
            }
          }               
      }
  }

This is suboptimal from my standpoint, but it works.

Mike
  • 777
  • 3
  • 7
  • 14