Search code examples
androidandroid-layoutkotlinandroid-view

Android custom edit text value is changed by another custom edit text


Intro

In one of my project I tried to create custom EditText with header and some custom validations. I came into a strange problem when I tested this custom view with screen rotation and activity recreation.

What is problem

Before recreation

When app starts all edit text have correct values which were set statically from activity. As on picture bellow:

enter image description here

After recreation

After I rotate screen or recreate activity EditText's values will be messed up. CustomEditText values are set to value of last edit text in XML. Simple (Basic Android EditText) edit text values are set normally.

enter image description here

Codes

I copied codes from project where this problem occurs.

MainActivity

class MainActivity : AppCompatActivity() {

     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)

         first_custom_edit_text.header = "First header"
         first_custom_edit_text.setText("First text")

         third_custom_edit_text.header = "Third header"
         third_custom_edit_text.setText("Third text")

         first_simple_edit_text.setText("First simple - Not affected")

         second_custom_edit_text.header = "Second header"
         second_custom_edit_text.setText("Second text")

         second_simple_edit_text.setText("Second simple - Not affected")
     }
}

CustomEditText

class CustomEditText : LinearLayout {
    fun setText(value: String?){
        this.input_edit_text.text = Editable.Factory.getInstance().newEditable(value ?: "")
    }

    fun getText(): String {
        return this.input_edit_text.text.toString()
    }

    var header: String?
        get() = this.header_text_view.text.toString()
        set(value) {
            this.header_text_view.text = Editable.Factory.getInstance().newEditable(value ?: "")
        }

    constructor(context: Context) : super(context){
        init(context, null)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs){
        init(context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init(context, attrs)
    }

    private fun init(context: Context, attrs: AttributeSet?) {
        inflate(context, R.layout.ui_custom_edit_text, this)
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <com.example.customedittextbug.CustomEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/first_custom_edit_text"/>

    <com.example.customedittextbug.CustomEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/second_custom_edit_text"/>

    <EditText
        tools:hint="[email protected]"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="-4dp"
        android:layout_marginRight="-4dp"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        android:inputType="text"
        android:id="@+id/first_simple_edit_text"/>

    <com.example.customedittextbug.CustomEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/third_custom_edit_text"/>


    <EditText
        tools:hint="[email protected]"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="-4dp"
        android:layout_marginRight="-4dp"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        android:inputType="text"
        android:id="@+id/second_simple_edit_text"/>

</LinearLayout>

ui_custom_edit_text.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    <TextView
            tools:text="Input header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textStyle="bold"
            android:textSize="17sp"
            android:id="@+id/header_text_view"/>
    <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:id="@+id/validations_errors_holder"/>
    <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/common_input_holder">
        <EditText
                tools:hint="[email protected]"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="-4dp"
                android:layout_marginRight="-4dp"
                android:textColor="@android:color/black"
                android:textSize="18sp"
                android:inputType="text"
                android:id="@+id/input_edit_text"/>
        <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignEnd="@+id/input_edit_text"
                android:layout_centerVertical="true"
                android:layout_marginEnd="4dp"
                android:layout_marginStart="4dp"
                android:gravity="end"
                android:orientation="horizontal"
                android:id="@+id/right_view_holder"/>
    </RelativeLayout>
</LinearLayout>

UPDATE

I found those two guides with nice explanation how to fix this problem after my question was answered.

Link1, Link2


Solution

  • State restoration is keyed by ID, and all of your custom views have a sub-View with the same ID: input_edit_text. Thus, they all get restored to the same state because they all got the last one that was saved under that ID.

    You could avoid this by setting android:saveEnabled="false" on that EditText (though you'll probably want to do the save/restore of instance state yourself in your CustomEditText).