5

I'm wondering if anyone else has experienced this strange bug. My layout is complicated but essentially it's rows of view holders with EditText fields.

It worked flawlessly until I updated my target SDK to API 30.

After targetting API 30, some of my EditText fields don't respond to "focus touch events" (the cursor handle doesn't show up, you can't long press, and you can't select any text). You can still type in the field and move the cursor by tapping. Again, some text fields work perfectly fine while others are affected by this bug.

enter image description here

^ The image is not of my app, just to show the cursor handler for understanding.

Both the constraint layout and the linear layout have the same bug.

<!-- in constraint layout -->
<androidx.appcompat.widget.AppCompatEditText
    android:id="@+id/myTextField"
    android:layout_height="wrap_content"
    android:layout_width="0dp"
    app:layout_constraintHorizontal_weight="1"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"/>

<!-- in linear layout -->
<androidx.appcompat.widget.AppCompatEditText
    android:id="@+id/myTextField"
    android:layout_height="wrap_content"
    android:layout_width="0dp"
    android:layout_weight="1"/>

If I change the text fields width to wrap_content it works perfectly for all text fields, but that doesn't fit with my design. I need the text field to fill the available space.

I even tried manually setting the width to a random dp value and it still didn't work.

I've tested on < API 30 devices and it's totally fine.

I've had users report this problem in production as well.

Did I miss something? Why isn't this working on API 30+?

Is there another way to get the EditText field to fill the space?

Update (still not solved):

I've updated my Android Studio to the latest version Arctic Fox 2020.3.1.. I've also updated gradle to 7.0.3, and kotlin to 1.5.31 and all my libraries to the latest version. I'm using androidx.core:core-ktx:1.7.0 and androidx.constraintlayout:constraintlayout:2.1.2.

I've investigated further and found further weirdness. Setting any text or any hint programmatically, causes the bug. Setting the text or hint in xml, totally fine and bug free. The text values are coming from the database, so I need to be able to set them programmatically.

// setting hint or text programmatically in .kt file causes the bug
// any one of the lines below will cause the bug

myTextField.setHint("myTextField hint")
myTextField.hint = "myTextField hint"
myTextField.setText("myTextField text")
myTextField.text = "myTextField text"

// setting hint or text in .xml file does not cause the bug

<androidx.appcompat.widget.AppCompatEditText
    android:id="@+id/myTextField"
    ...
    android:hint="myTextField hint"
    android:text="myTextField text"
    ... />

Update 2 (still not solved):

I opened an issue with Google but they were unable to resolve the issue yet.

https://issuetracker.google.com/issues/209938888

Lifes
  • 1,226
  • 2
  • 25
  • 45
  • It might be relevant to check what version of androidx you are using, and try to update that. Also, please make it more clear whether both the linear layout and constraintlayout version have this problem. Finally, it might help to post a larger part of the layout. Unrelated: toLeftOf is oldfashioned. use toStartOf – Nino van Hooff Dec 03 '21 at 13:59
  • I'm using `androidx.core:core-ktx:1.7.0'` and `androidx.constraintlayout:constraintlayout:2.1.2'`. I've also updated the question to be more clear that both layouts have the bug and switched leftOf to startOf. – Lifes Dec 06 '21 at 22:03
  • 1
    @Yunnosch I've moved the hacky alternate solution out of the question. It muddled the question and made it too long. Thanks for the suggestion! – Lifes Sep 12 '22 at 08:48

2 Answers2

1

I have tried to replicate your issue. It works fine with targetSdk 30 with updates to your constraints. Please find screenshots below. These are the changes I made.

  1. I updated the constraint properties you were using as they are outdated. Eg. I changed "layout_constraintLeft_toRightOf" to "layout_constraintStart_toStartOf".
  2. The way you declared your constraint properties puts the view out of the visible space of parent. Eg. 'app:layout_constraintLeft_toRightOf="parent"' Puts the leftside of the view to the end of the parent and that makes most of the view invisible.

I updated how you set your constraints based the code you sent in your question. Find the updated code below. You can also look at the screenshots below to see that it works.

        <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
        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=".Activities.MainActivity">

        <!-- THIS IS THE CONSTRAINT LAYOUT-->

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/the_constraintlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintHorizontal_weight="1"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/the_lineartlayout">

            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/myTextField"
                android:layout_height="wrap_content"
                android:layout_width="0dp"
                android:text="ConstraintLayout Sample Text"
                android:gravity="center"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"/>

            <!--
            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/myTextField"
                android:layout_height="wrap_content"
                android:layout_width="0dp"
                android:text="This is a sample text"
                android:gravity="center"
                app:layout_constraintHorizontal_weight="1"
                app:layout_constraintLeft_toRightOf="parent"
                app:layout_constraintRight_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"/>
                -->
        </androidx.constraintlayout.widget.ConstraintLayout>

        <!-- THIS IS THE LINEAR LAYOUT-->
        <androidx.appcompat.widget.LinearLayoutCompat
            android:id="@+id/the_lineartlayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/the_constraintlayout"
            app:layout_constraintBottom_toBottomOf="parent">
            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/myTextField2"
                android:layout_height="wrap_content"
                android:text="LinearLayoutCompat Sample Text"
                android:layout_width="0dp"
                android:layout_weight="1"/>
        </androidx.appcompat.widget.LinearLayoutCompat>
    </androidx.constraintlayout.widget.ConstraintLayout>

enter image description here enter image description here enter image description here

cmak
  • 576
  • 1
  • 4
  • 15
Junior
  • 1,007
  • 4
  • 16
  • 26
  • If you feel this does not help you resolve the issue, please post the entire xml code in the question so I can have a better look at it – Junior Dec 03 '21 at 16:30
  • why not use a flat XML structure? without nested constraint layouts? – Shark Dec 03 '21 at 17:20
  • 2
    It's a RecyclerView where each view holder has it's own EditText view. It only happens to a couple of view holders each time (it's random). I will try out your changes and see if they do anything. – Lifes Dec 03 '21 at 18:08
  • I made a mistake in the example code I posted and I've fixed it (so your second point was not relevant to my actual code). I tried out the changes and it made no difference. I am still seeing the same bug. – Lifes Dec 06 '21 at 19:12
0

I never found a fix. Here's a hacky alternate layout solution that worked for my needs and let me keep my design despite the bug. The bug does not happen with this alternate layout (since I use wrap_content for the actual edit text view and that prevents the bug from happening).

design:

[[TEXT FIELD] WRAPPER ]
[    FAKE UNDERLINE   ]

I ended up wrapping my text field in a LinearLayout and stretching that layout to fill the space the text field should have occupied. I made the text field wrap_content and set the background transparent (to maintain the spacing the edit text provides) and created a separate underline view that stretched the entire length of the wrapper. I programmatically redirected touch events from the wrapper to the end of the text field (since if they click the wrapper they're effectively touching the end of the text field).

Note: the magic numbers are just numbers that lined up my fake underline with the original edit text underline. When the original edit text is activated, the underline is a tiny bit bigger and I kept that for my fake underline. Use whatever numbers you like that look nice to you.

xml layout:

<LinearLayout
    android:id="@+id/editTextWrapper"
    android:layout_height="wrap_content"
    android:layout_width="0dp"
    android:layout_weight="1"
    app:layout_constraintHorizontal_weight="1"
    android:orientation="vertical"
    app:layout_constraintStart_toEndOf="@id/whateverView"
    app:layout_constraintEnd_toStartOf="@id/whateverView"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent">

    <androidx.appcompat.widget.AppCompatEditText
        android:id="@+id/editText"
        android:backgroundTint="@android:color/transparent"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:inputType="textMultiLine|textCapSentences"
        android:textSize="16sp"
        android:textColor="?android:textColorPrimary"
        android:textColorHint="?attr/textColorMuted"
        android:fontFamily="sans-serif-condensed"/>

</LinearLayout>

<!-- note: I tried setting this as the background of the editTextWrapper 
     instead of its own view but it didn't work in <= API 21 Lollipop -->
<View
    android:id="@+id/descriptionEditTextUnderline"
    android:background="@drawable/edit_text_underline_bg"
    android:layout_width="0dp"
    android:layout_height="5dp"
    android:layout_marginBottom="-5dp"
    android:translationY="-8.5dp"
    android:layout_marginHorizontal="3.5dp"
    app:layout_constraintStart_toStartOf="@id/editTextWrapper"
    app:layout_constraintEnd_toEndOf="@id/editTextWrapper"
    app:layout_constraintTop_toBottomOf="@id/editTextWrapper"
    app:layout_constraintBottom_toBottomOf="@id/editTextWrapper"/>

edit_text_underline_bg.xml (for lollipop)

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <selector>
            <!-- active state -->
            <item android:state_activated="true">
                <shape android:shape="line">
                    <stroke
                        android:color="?attr/colorControlActivated"
                        android:width="2dp"/>
                </shape>
            </item>
            <!-- regular state -->
            <item>
                <shape android:shape="line">
                    <stroke
                        android:color="?attr/colorControlNormal"
                        android:width="1dp"/>
                </shape>
            </item>
        </selector>
    </item>
</layer-list>

code (kotlin):

editText.setOnFocusChangeListener { view, hasFocus ->
    // manually update the underline state
    syncUnderlineState(hasFocus)
}
// redirect touch events from the wrapper to end of actual text field programmatically
editTextWrapper.setOnTouchListener { _, motionEvent ->
    val updatedMotionEvent = MotionEvent.obtain(
        motionEvent.downTime,
        motionEvent.eventTime,
        motionEvent.action,
        editTextUnderline.width.toFloat(), // change touch event x the end of the description edit text field
        motionEvent.y,
        motionEvent.metaState
    )
    editText.dispatchTouchEvent(updatedMotionEvent)
    true
}

private fun syncUnderlineState(doesEditTextHaveFocus: Boolean) {
    editTextUnderline.isActivated = doesEditTextHaveFocus
    if (doesEditTextHaveFocus) {
        editTextUnderline.translationY = UNDERLINE_FOCUSED_TRANSLATION_Y
        editTextUnderline.updateLayoutParams<ConstraintLayout.LayoutParams> {
            updateMargins(left = UNDERLINE_FOCUSED_MARGIN_HORIZONTAL, right = UNDERLINE_FOCUSED_MARGIN_HORIZONTAL)
        }
    } else {
        editTextUnderline.translationY = UNDERLINE_TRANSLATION_Y
        editTextUnderline.updateLayoutParams<ConstraintLayout.LayoutParams> {
            updateMargins(left = UNDERLINE_MARGIN_HORIZONTAL, right = UNDERLINE_MARGIN_HORIZONTAL)
        }
    }
}

companion object {
    private val UNDERLINE_TRANSLATION_Y = (-8.5F).dpToPixels.toFloat() // keep in sync with task_list_item.xml
    private val UNDERLINE_MARGIN_HORIZONTAL = 3.5F.dpToPixels // keep in sync with task_list_item.xml
    private val UNDERLINE_FOCUSED_TRANSLATION_Y = (-8.25F).dpToPixels.toFloat()
    private val UNDERLINE_FOCUSED_MARGIN_HORIZONTAL = 3F.dpToPixels
}

val Float.dpToPixels get() = (this * Resources.getSystem().displayMetrics.density).toInt()

If you're not supporting API <= 21, you can move the margins and translations from the underline view to the xml drawable and set it as the background of the wrapper.

Lifes
  • 1,226
  • 2
  • 25
  • 45