'Programmatically add view into a LinearLayout

I am trying to simulate a ListView in a LinearLayout in order to show three rows with identical layout. This single View is composed by a ratingbar and a content. It is very strange, but all ratingBars receive the last assigned value. First of all this is my custom Component that extends LinearLayout just adding these two methods:

public void setElements(List<Item> elements) {
    removeAllViews();
    for (int i = 0; i < elements.size() && i < 3; i++) {
        View vi = buildElementView(elements.get(i));
        vi.setId(i);
        addView(vi);
    }
}

private View buildElementView(Item itemElement) {
    View view = inflater.inflate(R.layout.element_list_item, null, true);
    // set values
    View header = view.findViewById(R.id.header);
    RatingBar ratingInItem = (RatingBar) header.findViewById(R.id.ratingBarInItem);
    TextView content = (TextView) view.findViewById(R.id.content);

    content.setText(itemElement.getContent());

    ratingInItem.setRating(itemElement.getRating());
    return view;
}

and this is the layout I am inflating:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.weorder.client"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@color/transparent"
    android:minHeight="60.0dp"
    android:orientation="vertical"
    android:paddingBottom="8.0dp"
    android:paddingLeft="5.0dp"
    android:paddingRight="5.0dp"
    android:paddingTop="8.0dp" >

    <RelativeLayout
        android:id="@+id/header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <RatingBar
            android:id="@+id/ratingBarInItem"
            style="@style/RatingBarSm"
            android:isIndicator="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="3dp"
            android:numStars="5"
            android:stepSize="0.1" />

    </RelativeLayout>

    <TextView
        android:id="@+id/content"
        style="@style/Content"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="3dp"
        android:layout_marginRight="3dp"
        android:layout_marginTop="3dp"

        android:textColor="@color/dark_brown"
        android:textSize="@dimen/text_size_small" />

 </LinearLayout>

I call the setElements method inside onActivityCreated() in the fragment. It works well when the fragment start, but when I try to rotate the phone, the content of items changes properly but the ratingbar gets the last value (if there are 3 elements with rating 1,2 and 3, all ratingbars have 3 stars). Is it a bug?

EDIT: this is my onSaveInstanceMethod:

    @Override
public void onSaveInstanceState(Bundle outState) {

    if (elements != null) {
        outState.putSerializable("elements", elements);
    }

    super.onSaveInstanceState(outState);
}

Thanks



Solution 1:[1]

I think you are using the findViewById() with the wrong view which is causing issues.

Here is a sample based on what you are doing that works for me.

LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
ViewGroup parentGroup = parentGroup = (ViewGroup)ll;

for (int i = 0; i < 3; i++)
{
    LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.element_list_item, null, true);
    RatingBar rBar = (RatingBar)view.findViewById(R.id.ratingBar1);
    rBar.setProgress(i);
    parentGroup.addView(view);
}

The results should show three rating bars with 0, 1, and 2 stars depending on how you set up the rating bar. I attach the new view to the view parent three times. The parent is my linearlayout and the new view is the rating bar (or whatever you choose).

For saving in fragments use

@Override
public void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    getFragmentManager().putFragment(outState, Content.class.getName(), this);
}

then you can get your arguments in the onCreateView and restore your data (if necessary)

Bundle extras = this.getArguments();
if(extras != null)
{
    //set arguments here
}

Solution 2:[2]

It works well when the fragment start, but when I try to rotate the phone, the content of items changes properly but the ratingbar gets the last value (if there are 3 elements with rating 1,2 and 3, all ratingbars have 3 stars). Is it a bug?

This is not a bug - this is how Android works.

Why is this happening?

By default, the system will save and restore the state of views that have IDs and it uses those IDs to maintain the internal map of state. Because you are using the same layout in the LinearLayout, each child has a RatingBar with the ID ratingBarInItem. When the system saves your view's state, it saves the state of the first bar, then overwrites it with the second bar, then overwrites that with the third bar. Then, when you restore state, it restores every view with the ID ratingBarInItem to what it has in the state map - which will be the last value saved, the value from the 3rd item.

How do you fix this?

You have two options.

  1. Manually save and restore the state of the views yourself.

You show that you are already saving the "elements" that are in the view. Well, you could update onRestoreInstanceState to read those elements back then use them to re-update the child views (i.e., just call setElements again).

  1. Give each rating bar a unique ID.

After you inflate a child view to add to the layout, use generateViewID to create a new, unique ID you can set on each RatingBar.

RatingBar ratingInItem = (RatingBar) header.findViewById(R.id.ratingBarInItem);
ratingInItem.setID(View.generateViewID());

Then you don't have to implement saving or restoring state because, now that each view has a unique ID, the default system behavior will work.

Hope that helps!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 dominicoder