Custom TextInputLayout gets wrong values set on back pressed

561 Views Asked by At

In my app I'm using custom view containing a TextInputLayout with the following code:

    <?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout"
        style="@style/UbiInput2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="-"
        android:orientation="horizontal"
        app:boxBackgroundColor="@color/white">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/input_value"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:inputType="textPersonName" />

    </com.google.android.material.textfield.TextInputLayout>
</androidx.appcompat.widget.LinearLayoutCompat>

And the kotlin

      class TextInputView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
        
            var errorMessage: String? = ""
        
            init {
                inflate(context, R.layout.text_input_view, this)
                val attributes = context.obtainStyledAttributes(attrs, R.styleable.TextInputView)
                val inputLayout = findViewById<TextInputLayout>(R.id.input_layout)
                val inputValue = findViewById<TextInputEditText>(R.id.input_value)
                inputLayout.hint = attributes.getString(R.styleable.TextInputView_hint)
                inputValue.hint = attributes.getString(R.styleable.TextInputView_placeholderText)
                inputLayout.isExpandedHintEnabled =
                    attributes.getBoolean(R.styleable.TextInputView_expandedHintEnabled, true)
                errorMessage = attributes.getString(R.styleable.TextInputView_errorMessage)
                inputLayout.isHelperTextEnabled = false
                inputValue.inputType =
                    attributes.getInt(
                        R.styleable.TextInputView_android_inputType,
                        InputType.TYPE_CLASS_TEXT
                    )
                if (attributes.hasValue(R.styleable.TextInputView_android_maxLength)) {
                    inputValue.filters += InputFilter.LengthFilter(
                        attributes.getInt(
                            R.styleable.TextInputView_android_maxLength,
                            100
                        )
                    )
                }
                inputValue.gravity =
                    attributes.getInt(R.styleable.TextInputView_android_gravity, Gravity.START)
                if (attributes.getBoolean(R.styleable.TextInputView_android_gravity, false)) {
                    inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
                }
                if (attributes.getBoolean(R.styleable.TextInputView_helperTextEnabled, false)) {
                    inputLayout.isHelperTextEnabled = true
                    inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
                }
        
                inputLayout.startIconDrawable =
                    attributes.getDrawable(R.styleable.TextInputView_startIconDrawable)
                 attributes.recycle()
            }
    }

@BindingAdapter("textValue")
fun TextInputView.setTextValue(value: String?) {
    value?.let {
        setValue(value)
    }
}

@InverseBindingAdapter(attribute = "textValue")
fun TextInputView.getTextValue(): String {
    return value()
}

@BindingAdapter("textValueAttrChanged")
fun TextInputView.setListener(textAttrChanged: InverseBindingListener) {
    val inputValue = findViewById<TextInputEditText>(R.id.input_value)

    inputValue.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }

        override fun afterTextChanged(s: Editable?) {
            textAttrChanged.onChange()
        }
    })
}

I have the following screen which go to the next fragment on button click. Using Navigation component.

enter image description here

But from the next screen, when I'm pressing back button to come back to the search form, the TextInputLayout values for hint, and helperText are all the same. The only place I set those is inside the custom view. And from debugging I can see that all the correct values are set at that time.

enter image description here

I'm a bit out of ideas about what's going on there. Any hints would be appreciated.

Material library version used: 1.3.0-alpha04

All codebase has been migrated to view binding according the latest changes

The View is used as followed:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="viewModel"
            type="com.ubigo.features.v2.products.taxi.SearchViewModel" />

        <variable
            name="dateFormat"
            type="com.ubigo.ubicore.date.UbiDate" />
    </data>
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/rental_search_root"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/background">
        
        <androidx.cardview.widget.CardView
            android:id="@+id/rental_search_form"
            style="@style/WhiteCardView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView10">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/constraintLayout3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <com.ubigo.ubicore.ui.TextInputView
                    android:id="@+id/pickup"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_marginTop="8dp"
                    android:layout_marginEnd="8dp"
                    app:hint="@string/product_start"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.0"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:startIconDrawable="@drawable/ic_calendar"
                    app:textValue="@{dateFormat.getPrettyDate(viewModel.pickupDateTime)}" />

                <View
                    android:id="@+id/divider6"
                    android:layout_width="0dp"
                    android:layout_height="1dp"
                    android:layout_marginStart="8dp"
                    android:layout_marginTop="8dp"
                    android:layout_marginEnd="8dp"
                    android:background="?android:attr/listDivider"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/pickup" />

                <com.ubigo.ubicore.ui.TextInputView
                    android:id="@+id/dropoff"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_marginTop="8dp"
                    android:layout_marginEnd="8dp"
                    android:layout_marginBottom="8dp"
                    app:hint="@string/product_end"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/divider6"
                    app:startIconDrawable="@drawable/ic_calendar"
                    app:textValue="@{dateFormat.getPrettyDate(viewModel.destinationDatetime)}" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>

        <com.ubigo.ubicore.ui.LoaderButton
            android:id="@+id/rental_search_button"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="32dp"
            android:layout_marginEnd="16dp"
            android:text="@string/product_rental_show_car"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/rental_search_location" />

        <androidx.cardview.widget.CardView
            android:id="@+id/rental_search_location"
            style="@style/WhiteCardView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="32dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/rental_search_form">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/constraintLayout4"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <com.ubigo.ubicore.ui.TextInputView
                    android:id="@+id/user_location"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_marginTop="8dp"
                    android:layout_marginEnd="8dp"
                    android:layout_marginBottom="8dp"
                    app:helperText="@string/product_rental_location"
                    app:helperTextEnabled="true"
                    app:hint="@string/product_pickup_location"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:startIconDrawable="@drawable/ic_location_on_black_24dp"
                    app:textValue="@{viewModel.depAddress}" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
        
        <TextView
            android:id="@+id/textView10"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="32dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="32dp"
            android:text="@string/product_rental_weekend"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

And the initialization of the Fragment:

class RentalSearchFragment : Fragment() {

    val viewModel: SearchViewModel by viewModel()

    private val binding get() = _binding!!
    private var _binding: FragmentRentalSearchBinding? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (_binding == null) {
            _binding = FragmentRentalSearchBinding.inflate(inflater, container, false)
            binding.lifecycleOwner = viewLifecycleOwner
            binding.viewModel = viewModel
            binding.dateFormat = UbiDate()
        }

        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
1

There are 1 best solutions below

3
On

I was able to solve this exact problem by implementing onSaveInstanceState, onRestoreInstanceState, dispatchSaveInstanceState, dispatchRestoreInstanceState, and setValues as described here

http://www.devexchanges.info/2016/03/custom-compound-view-in-android.html

The linked above goes into all the necessary detail about how to implement these functions. There's also undoubtedly countless other sources you can find about how to implement this android paradigm.