Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 685
Michal Zhradnk Nono3551 Avatar asked May 11 '26 23:05

Michal Zhradnk Nono3551


2 Answers

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).

like image 137
Ryan M Avatar answered May 14 '26 13:05

Ryan M


I was tired of searching but this worked for me.

add to CustomEditText class

companion object {
    private const val SPARSE_STATE_KEY = "SPARSE_STATE_KEY"
    private const val SUPER_STATE_KEY = "SUPER_STATE_KEY"
}



override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
    dispatchFreezeSelfOnly(container)
}

override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
    dispatchThawSelfOnly(container)
}

override fun onSaveInstanceState(): Parcelable? {
    Log.i("ByHand", "onSaveInstanceState")
    return Bundle().apply {
        Log.i("ByHand", "Writing children state to sparse array")
        putParcelable(SUPER_STATE_KEY, super.onSaveInstanceState())
        putSparseParcelableArray(SPARSE_STATE_KEY, saveChildViewStates())
    }
}

override fun onRestoreInstanceState(state: Parcelable?) {
    Log.i("ByHand", "onRestoreInstanceState")
    var newState = state
    if (newState is Bundle) {
        Log.i("ByHand", "Reading children children state from sparse array")
        val childrenState = newState.getSparseParcelableArray<Parcelable>(SPARSE_STATE_KEY)
        childrenState?.let { restoreChildViewStates(it) }
        newState = newState.getParcelable(SUPER_STATE_KEY)
    }
    super.onRestoreInstanceState(newState)
}


fun ViewGroup.saveChildViewStates(): SparseArray<Parcelable> {
    val childViewStates = SparseArray<Parcelable>()
    children.forEach { child -> child.saveHierarchyState(childViewStates) }
    return childViewStates
}

fun ViewGroup.restoreChildViewStates(childViewStates: SparseArray<Parcelable>) {
    children.forEach { child -> child.restoreHierarchyState(childViewStates) }
}

this link for details

like image 28
Kerem Türker Avatar answered May 14 '26 15:05

Kerem Türker