material-components / material-components-android

Modular and customizable Material Design UI components for Android
Apache License 2.0
16.29k stars 3.06k forks source link

[TabLayout] Default tab text appearance is incorrectly setting attributes for text appearance instead of style #3440

Open geoff-powell opened 1 year ago

geoff-powell commented 1 year ago

Description: When updating to 1.9.0 of material, we noticed an error when taking Paparazzi snapshot tests using a TabLayout. There was a change from 1.7.0 -> 1.9.0 where defaultTabTextAppearance was used.

Stacktrace

Jun 06, 2023 3:19:08 PM app.cash.paparazzi.internal.PaparazziLogger error
SEVERE: null: Style ResourceReference{namespace=apk/res-auto, type=attr, name=textAppearanceButton} is not of type STYLE (instead attr)
Jun 06, 2023 3:19:08 PM app.cash.paparazzi.internal.PaparazziLogger error
SEVERE: null: Style ResourceReference{namespace=apk/res-auto, type=attr, name=textAppearanceButton} is not of type STYLE (instead attr)
Jun 06, 2023 3:19:08 PM app.cash.paparazzi.internal.PaparazziLogger error
SEVERE: resources.resolve: Failed to find style with 2130969720

Cannot invoke "android.content.res.BridgeTypedArray.setTheme(android.content.res.Resources$Theme)" because "ta" is null
java.lang.NullPointerException: Cannot invoke "android.content.res.BridgeTypedArray.setTheme(android.content.res.Resources$Theme)" because "ta" is null
    at android.content.res.Resources_Theme_Delegate.obtainStyledAttributes(Resources_Theme_Delegate.java:74)
    at android.content.res.Resources$Theme.obtainStyledAttributes(Resources.java:1631)
    at android.content.Context.obtainStyledAttributes(Context.java:874)
    at android.widget.TextView.setTextAppearance(TextView.java:3965)
    at androidx.appcompat.widget.AppCompatTextView.setTextAppearance(AppCompatTextView.java:216)
    at android.widget.TextView.setTextAppearance(TextView.java:3954)
    at androidx.core.widget.TextViewCompat.setTextAppearance(TextViewCompat.java:289)
    ...
    at com.google.testing.junit.testparameterinjector.PluggableTestRunner$ContextMethodRule$1.evaluate(PluggableTestRunner.java:429)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:108)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:40)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:60)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:52)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at jdk.proxy1/jdk.proxy1.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)

Expected behavior: Correctly load styles from attributes using something like this

val attrs = intArrayOf(defaultTabTextAppearance)       // The array of attributes we're interested in.
val ta = context.obtainStyledAttributes(attrs)        // Get the value referenced by the attributes in the array
val resId = ta.getResourceId(0, 0)                    // The first 0 is the index in the 'attrs' array.
ta.recycle()                                          // Don't forget that! You can also use TypedArray.use { } extensions from android KTX.
TextViewCompat.setTextAppearance(textView, resId)     // Utility method to set text appearance for all SDK versions

Source code: The code snippet which is causing this issue

class SampleTest {
  @get:Rule
  val paparazzi = Paparazzi(
    deviceConfig = DeviceConfig.PIXEL_2.copy(fontScale = accessibilityTextSize.scale),
  )

  private val context: Context by lazy { paparazzi.context.wrapWithTheme { design.provide(this) } }

  @Test
  fun default() {
    val view = TabLayout(context, null)
    println("textAppearanceButton ID: " + com.google.android.material.R.attr.textAppearanceButton)
    TextViewCompat.setTextAppearance(textView, com.google.android.material.R.attr.textAppearanceButton)

    paparazzi.snapshot(view)
  }
}

Minimal sample app repro: TBD

Android API version: 32 (via Paparazzi)

Material Library version: 1.9.0

Device: (Run via junit tests with Paparazzi)

Asuveroz commented 1 year ago

Same problem. Also happens in version 1.8.0.

Bunch of render problems with this simple XML code:

<com.google.android.material.tabs.TabLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.google.android.material.tabs.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tab 1" />

    <com.google.android.material.tabs.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tab 2" />

    <com.google.android.material.tabs.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tab 3" />
</com.google.android.material.tabs.TabLayout>

Style ResourceReference{namespace=apk/res-auto, type=attr, name=textAppearanceTitleSmall} is not of type STYLE (instead attr)

java.lang.NullPointerException: Cannot invoke "android.content.res.BridgeTypedArray.setTheme(android.content.res.Resources$Theme)" because "ta" is null

TomGarden commented 1 year ago

Yes the version problem ; try switch to implementation "com.google.android.material:material:1.7.0"

hamidreza-sani commented 8 months ago

This is still an issue with build 1.11.0