1

I'm trying to write my own viewMatcher testing the background color of my Button

so I'm trying to implement: Testing background color espresso Android

here's my code and how I call it


fun withBackColor(color: Int): Matcher<View?> {
    Checks.checkNotNull(color)
    return object : BoundedMatcher<View?, Button>(Button::class.java) {
        override fun matchesSafely(button: Button): Boolean {
            val col = (button.background as ColorDrawable).color
            return color == col
        }

        override fun describeTo(description: org.hamcrest.Description) {
            description.appendText("with text color: ")
        }
    }
}
onView(withId(R.id.continueBtn)).check(matches(withBackColor(R.color.custom_color_slider_widget_unselected)))

when I run this code I get: java.lang.ClassCastException: android.graphics.drawable.RippleDrawable cannot be cast to android.graphics.drawable.ColorDrawable

This is my button:

<Button
    android:id="@+id/continueBtn"
    style="@style/ButtonGreyStyle"
    android:layout_marginBottom="25dp"
    android:text="@string/btn_continue"
    android:textColor="@color/white"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

The way it's set is in the on create of the fragment even though the button gets created in an activity launched before

continue_btn?.setBackgroundColor(
    ContextCompat.getColor(
        requireActivity(),
        R.color.custom_color_slider_widget_unselected
    )
)

The style property of the color


<color name="custom_color_slider_widget_unselected">#33EBF5F9</color>

I searched google and found this

Cannot be cast to android.graphics.drawable.ColorDrawable

but didn't find any link to something I could be doing wrong

EDIT:

    override fun matchesSafely(button: Button): Boolean {
        val col = button.background
        val state: ConstantState? = col.constantState
        return try {
            val colorField: Field = state?.javaClass!!.getDeclaredField("mColor")
            colorField.isAccessible = true
            val colorState: ColorStateList = colorField.get(state) as ColorStateList
            val rippleColor: Int = colorState.defaultColor
            color == rippleColor
        } catch (e: NoSuchFieldException) {
            e.printStackTrace()
            false
        } catch (e: IllegalAccessException) {
            e.printStackTrace()
            false
        }
    }

I'm now trying to get the color of the RippleDrawable without a cast to ColorDrawable but i get the NoSuchFieldException

Accessing hidden field Landroid/graphics/drawable/RippleDrawable$RippleState;->mColor:Landroid/content/res/ColorStateList; (max-target-r, reflection, denied) 11-16 14:10:21.227 19473 19473 W System.err: java.lang.NoSuchFieldException: No field mColor in class Landroid/graphics/drawable/RippleDrawable$RippleState; (declaration of 'android.graphics.drawable.RippleDrawable$RippleState' appears in /system/framework/framework.jar)

debug image

debug image

Neyross
  • 23
  • 5
  • `if (button.background is ColorDrawable)` Kotlin's version for Java's ***instanceof***? – ecle Nov 15 '22 at 08:45
  • `id 'org.jetbrains.kotlin.android' version '1.7.20' apply false` is what i have in my build.gradle – Neyross Nov 15 '22 at 08:46
  • Use Kotlin ***is*** operator to test the object of a given type the same way as Java ***instanceof*** see how this is applied in this issue https://github.com/facebook/react-native/commit/456cf3db14c443c483d63aa97c88b45ffd25799b?diff=split – ecle Nov 15 '22 at 09:07
  • i know my background is a Ripple drawable but can i cast it to colorDrawable as the background is only a color that way i can compare it for the viewmatcher ? – Neyross Nov 15 '22 at 09:14
  • 1
    Their parent **Drawable** has no ***getColor()*** or ***setColor()*** methods and does not implement any interface that does. As a result, **ColorDrawable** and **RippleDrawable** each have their own method for obtaining/changing color with a different parameter. Surprisingly, **RippleDrawable** does not support the ***getColor()*** method. As a result, the method for color getter/setter in this case differs between the two classes. – ecle Nov 15 '22 at 09:47
  • I see so I'm trying to get the color of the rippleDrawable by implementing something like this stackoverflow.com/questions/36352945/… but it goes in the noSuchFieldException which is weird because in debug I can see the field exists (see edit for then new code) – Neyross Nov 16 '22 at 01:09
  • Your most recent update refers to the reflection approach to accessing a static class's private field, which is a well-known solution on the Internet. I'm not sure why it's not working right now... – ecle Nov 16 '22 at 04:05
  • 1
    Refer this for the reflection issue https://stackoverflow.com/questions/15420968/nosuchfieldexception-when-field-exists – ecle Nov 16 '22 at 04:12
  • Accessing hidden field Landroid/graphics/drawable/RippleDrawable$RippleState;->mColor:Landroid/content/res/ColorStateList; (max-target-r, reflection, denied) 11-16 14:10:21.227 19473 19473 W System.err: java.lang.NoSuchFieldException: No field mColor in class Landroid/graphics/drawable/RippleDrawable$RippleState; (declaration of 'android.graphics.drawable.RippleDrawable$RippleState' appears in /system/framework/framework.jar) here's the error i have in more detail i tried accessing field trough superclass but got same error, invalidating caches and checking proguards rules they are commented – Neyross Nov 16 '22 at 05:12
  • seems weird but my button background has mBackground = null maybe that's a clue – Neyross Nov 16 '22 at 05:38
  • 1
    The ***mBackground*** is the private field in **RippleDrawable**. Its type is **RippleBackground**. The **RippleBackground** class has a private int field ***mColor***. Maybe you can use reflection to access this ***mBackground***'s ***mColor*** property. But, you found out it is null when ***mAddRipple*** is false... So, it is not useful https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/drawable/RippleDrawable.java – ecle Nov 16 '22 at 06:18
  • looks like i succeeded by adding the property background to the button in the layout and using the colorDrawable Way Thanks for your help – Neyross Nov 16 '22 at 06:38
  • Perhaps you could add a post to your solution for others to reference. – ecle Nov 16 '22 at 06:40
  • yeah i'll do that when getting home – Neyross Nov 16 '22 at 06:45

1 Answers1

0

The problem about not being able to cast the background as colorDrawable was that in my layout xml

<Button
   android:id="@+id/continueBtn"
   style="@style/ButtonGreyStyle"
   android:layout_marginBottom="25dp"
   android:text="@string/btn_continue"
   android:textColor="@color/white"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintStart_toStartOf="parent" />

the background element was not defined and even though it was modified in my kotlin adding to the layout

android:background="@color/custom_color_slider_widget_unselected"

made me able to cast in colorDrawable and then modifying the matcher to get the hex of color like so:

fun withBackColor(color: Int): Matcher<View?> {
Checks.checkNotNull(color)
return object : BoundedMatcher<View?, Button>(Button::class.java) {
    override fun matchesSafely(button: Button): Boolean {
        val backColor = (button.background as ColorDrawable).color
        val idColor = ContextCompat.getColor(button.context, color)
        val backHexColor = String.format("#%06X", 0xFFFFFF and backColor)
        val idHexColor = String.format("#%06X", 0xFFFFFF and idColor)
        return backHexColor == idHexColor
    }

    override fun describeTo(description: org.hamcrest.Description) {
        description.appendText("background color wrong")
    }
}

and I can now test for the color of the background of a button

Neyross
  • 23
  • 5