Search code examples
androidandroid-custom-view

ID of the child views in a custom view are same in Android


I have a custom view ValueSelectorView which contains two buttons to increment and decrement value and an EditText to display that value. In my activity, I am using this view three times. My concern here is with the child views that their id is always the same when checked in logs. Due to this, the resultant value comes out to be incorrect.

I have looked over on StackOverflow and found out that some of them have faced the same issue and they are trying to solve it by manually altering the id of the child views using setId. Tried inflating the child views using a couple of different methods but the issue still persists

This is my custom view class

package eu.siacs.conversations.ui.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.annotation.RequiresApi;

import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;

public class ValueSelectorView extends RelativeLayout {

    View rootView;
    EditText valueTextView;
    View minusButton;
    View plusButton;

    private int minValue = Integer.MIN_VALUE;
    private int maxValue = Integer.MAX_VALUE;

    public ValueSelectorView(Context context) {
        super(context);
        init(context, null);
    }

    public ValueSelectorView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public ValueSelectorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public ValueSelectorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        rootView = LayoutInflater.from(context).inflate(R.layout.value_selector, this, true);
        Log.i(Config.LOGTAG, "****view testr**** id of this view is -->" + getResources().getResourceEntryName(rootView.getId()));
        valueTextView = rootView.findViewById(R.id.valueTextView);

        minusButton = rootView.findViewById(R.id.minusButton);
        plusButton = rootView.findViewById(R.id.plusButton);

        minusButton.setOnClickListener(v -> decrementValue());

        plusButton.setOnClickListener(v -> incrementValue());

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ValueSelectorView,0,0);
        int defaultVaue = typedArray.getInt(R.styleable.ValueSelectorView_defaultValue,0);
        Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 0 -->" + valueTextView.getId());
//        Log.i(Config.LOGTAG, "****view testr**** id of this Minus Button is 0 -->" + minusButton.getId());
//        Log.i(Config.LOGTAG, "****view testr**** id of this Add Button is 0 -->" + plusButton.getId());
        valueTextView.setText(String.valueOf(defaultVaue));

        typedArray.recycle();
    }

    public void setDefaultValue(int value) {
        valueTextView.setText(String.valueOf(value));
    }

    public int getMinValue() {
        return minValue;
    }

    public void setMinValue(int minValue) {
        this.minValue = minValue;
    }

    public int getMaxValue() {
        return maxValue;
    }

    public void setMaxValue(int maxValue) {
        this.maxValue = maxValue;
    }

    public int getValue() {
        return Integer.valueOf(valueTextView.getText().toString());
    }

    /*public void setValue(int newValue) {
        int value = newValue;
        if(newValue < minValue) {
            value = minValue;
        } else if (newValue > maxValue) {
            value = maxValue;
        }
        Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 1 -->" + valueTextView.getId());
        valueTextView.setText(String.valueOf(value));
    }*/

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
    }

    private void incrementValue() {

        int currentVal = Integer.valueOf(valueTextView.getText().toString());
        if(currentVal < maxValue) {
            Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 2-->" + valueTextView.getId());
            valueTextView.setText(String.valueOf(currentVal + 1));
        }
    }

    private void decrementValue() {
        int currentVal = Integer.valueOf(valueTextView.getText().toString());
        if(currentVal > minValue) {
            Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 3-->" + valueTextView.getId());
            valueTextView.setText(String.valueOf(currentVal - 1));
        }
    }


}

This is how I use it in my activity XML.

<eu.siacs.conversations.ui.widget.ValueSelectorView
                android:id="@+id/value_selector_nights"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margin4"
                app:defaultValue="1"
                android:gravity="center_vertical"/>

<eu.siacs.conversations.ui.widget.ValueSelectorView
                    android:id="@+id/value_selector_adults"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/margin4"
                    app:defaultValue="1"
                    android:gravity="center_vertical"/>

<eu.siacs.conversations.ui.widget.ValueSelectorView
                    android:id="@+id/value_selector_children"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:defaultValue="0"
                    android:layout_marginTop="@dimen/margin4"
                    android:gravity="center_vertical"/>

Below are the logs:

****view testr**** id of this view is -->value_selector_nights
****view testr**** id of this TextView is 0 -->2131297384
****view testr**** id of this view is -->value_selector_adults
****view testr**** id of this TextView is 0 -->2131297384
****view testr**** id of this view is -->value_selector_children
****view testr**** id of this TextView is 0 -->2131297384

I would like to understand how it works internally and find a solution for the same. Thank you for your time.


Solution

  • For others, who are facing the same issue.

    My updated code is:

    package eu.siacs.conversations.ui.widget;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.os.Build;
    import android.os.Parcel;
    import android.os.Parcelable;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.util.SparseArray;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.EditText;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    
    import androidx.annotation.Nullable;
    import androidx.annotation.RequiresApi;
    
    import eu.siacs.conversations.Config;
    import eu.siacs.conversations.R;
    
    public class ValueSelectorView extends RelativeLayout {
    
        View rootView;
        EditText valueTextView;
        View minusButton;
        View plusButton;
        private int customState;
    
        private int minValue = Integer.MIN_VALUE;
        private int maxValue = Integer.MAX_VALUE;
    
        public ValueSelectorView(Context context) {
            super(context);
            init(context, null);
        }
    
        public ValueSelectorView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        public ValueSelectorView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context, attrs);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public ValueSelectorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            rootView = LayoutInflater.from(context).inflate(R.layout.value_selector, this, true);
            Log.i(Config.LOGTAG, "****view testr**** id of this view is -->" + getResources().getResourceEntryName(rootView.getId()));
            valueTextView = rootView.findViewById(R.id.valueTextView);
    
            minusButton = rootView.findViewById(R.id.minusButton);
            plusButton = rootView.findViewById(R.id.plusButton);
    
            minusButton.setOnClickListener(v -> decrementValue());
    
            plusButton.setOnClickListener(v -> incrementValue());
    
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ValueSelectorView,0,0);
            int defaultVaue = typedArray.getInt(R.styleable.ValueSelectorView_defaultValue,0);
            Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 0 -->" + valueTextView.getId());
    //        Log.i(Config.LOGTAG, "****view testr**** id of this Minus Button is 0 -->" + minusButton.getId());
    //        Log.i(Config.LOGTAG, "****view testr**** id of this Add Button is 0 -->" + plusButton.getId());
            valueTextView.setText(String.valueOf(defaultVaue));
    
            typedArray.recycle();
        }
    
        public void setDefaultValue(int value) {
            valueTextView.setText(String.valueOf(value));
        }
    
        public int getMinValue() {
            return minValue;
        }
    
        public void setMinValue(int minValue) {
            this.minValue = minValue;
        }
    
        public int getMaxValue() {
            return maxValue;
        }
    
        public void setMaxValue(int maxValue) {
            this.maxValue = maxValue;
        }
    
        public int getValue() {
            return Integer.valueOf(valueTextView.getText().toString());
        }
    
        /*public void setValue(int newValue) {
            int value = newValue;
            if(newValue < minValue) {
                value = minValue;
            } else if (newValue > maxValue) {
                value = maxValue;
            }
            Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 1 -->" + valueTextView.getId());
            valueTextView.setText(String.valueOf(value));
        }*/
    
        @Override
        public void addView(View child, int index, ViewGroup.LayoutParams params) {
            super.addView(child, index, params);
        }
    
        private void incrementValue() {
    
            int currentVal = Integer.valueOf(valueTextView.getText().toString());
            if(currentVal < maxValue) {
                Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 2-->" + valueTextView.getId());
                valueTextView.setText(String.valueOf(currentVal + 1));
            }
        }
    
        private void decrementValue() {
            int currentVal = Integer.valueOf(valueTextView.getText().toString());
            if(currentVal > minValue) {
                Log.i(Config.LOGTAG, "****view testr**** id of this TextView is 3-->" + valueTextView.getId());
                valueTextView.setText(String.valueOf(currentVal - 1));
            }
        }
    
        public void setCustomState(int customState) {
            this.customState = customState;
        }
    
        @Override
        protected Parcelable onSaveInstanceState() {
            Parcelable superState = super.onSaveInstanceState();
            SavedState ss = new SavedState(superState);
            ss.value = valueTextView.getText().toString();
            return ss;
        }
    
        @Override
        protected void onRestoreInstanceState(Parcelable state) {
            SavedState ss = (SavedState) state;
            super.onRestoreInstanceState(ss.getSuperState());
            valueTextView.setText(ss.value);
        }
    
        public static class SavedState extends BaseSavedState {
            String value;
    
            SavedState(Parcelable superState) {
                super(superState);
            }
    
            public SavedState(Parcel source) {
                super(source);
                value = source.readString();
            }
    
            @Override
            public void writeToParcel(Parcel dest, int flags) {
                super.writeToParcel(dest, flags);
                dest.writeString(value);
            }
    
            public static final Parcelable.Creator<SavedState> CREATOR =
                    new Parcelable.Creator<SavedState>() {
                        public SavedState createFromParcel(Parcel in) {
                            return new SavedState(in);
                        }
    
                        public SavedState[] newArray(int size) {
                            return new SavedState[size];
                        }
                    };
        }
    }
    
    

    Child view XML in my custom view

    <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="@dimen/medium_font"
            android:enabled="false"
            android:background="@null"
            android:textColor="#000000"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/minusButton"
            android:saveEnabled="false"
            android:id="@+id/valueTextView" />