Fragment isolation testing with Material Design Theme

Viewed 521

I have a setup of single activity, mutli feature module, that looks like this :

/app - A main module that contains the styles resource that inherits the material design ones, and the activity /menu - Module feature with just a fragment and viewmodel /other modules...

I am trying to run my Fragment in my Android Tests in isolation, which results in an error that the Material components (in this case CardView) have to have Material Theme as its style. Which does make sense, but I tried that.

Now I get this error

android.view.InflateException: Binary XML file line #21 in com.nikolam.menu.test:layout/menu_item: Binary XML file line #21 in com.nikolam.menu.test:layout/menu_item: Error inflating class com.google.android.material.card.MaterialCardView
Caused by: android.view.InflateException: Binary XML file line #21 in com.nikolam.menu.test:layout/menu_item: Error inflating class com.google.android.material.card.MaterialCardView
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at android.view.LayoutInflater.createView(LayoutInflater.java:854)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1006)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:1123)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1084)
at android.view.LayoutInflater.inflate(LayoutInflater.java:682)
at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
at com.nikolam.menu.ui.menu.adapter.MenuAdapter.onCreateViewHolder(MenuAdapter.kt:27)
at com.nikolam.menu.ui.menu.adapter.MenuAdapter.onCreateViewHolder(MenuAdapter.kt:16)
at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7266)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6397)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6281)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6277)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2330)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1631)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4277)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3980)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4546)
at android.view.View.layout(View.java:21912)
at android.view.ViewGroup.layout(ViewGroup.java:6260)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
at android.view.View.layout(View.java:21912)
at android.view.ViewGroup.layout(ViewGroup.java:6260)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:21912)
at android.view.ViewGroup.layout(ViewGroup.java:6260)
at com.android.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:509)
at android.view.View.layout(View.java:21912)
at android.view.ViewGroup.layout(ViewGroup.java:6260)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:779)
at android.view.View.layout(View.java:21912)
at android.view.ViewGroup.layout(ViewGroup.java:6260)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3080)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2590)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1721)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7598)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
at android.view.Choreographer.doCallbacks(Choreographer.java:790)
at android.view.Choreographer.doFrame(Choreographer.java:725)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.MaterialComponents (or a descendant).
at com.google.android.material.internal.ThemeEnforcement.checkTheme(ThemeEnforcement.java:240)
at com.google.android.material.internal.ThemeEnforcement.checkMaterialTheme(ThemeEnforcement.java:215)
at com.google.android.material.internal.ThemeEnforcement.checkCompatibleTheme(ThemeEnforcement.java:143)
at com.google.android.material.internal.ThemeEnforcement.obtainStyledAttributes(ThemeEnforcement.java:78)
at com.google.android.material.card.MaterialCardView.<init>(MaterialCardView.java:128)
at com.google.android.material.card.MaterialCardView.<init>(MaterialCardView.java:118)

Which is self-explanatory, but here's the issue this is the layout and that it needs to inherit the material design style. Which it does. This is only the issue while running it in an isolated environment (the Tests) it otherwise works fine. I am guessing that the Fragment in a container doesn't have reference to the style, but I then added it to the CardView itself which confuses me further.

@Before
    fun setUp() {
        launchFragmentInContainer<MenuFragment>()
    }
<?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>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.card.MaterialCardView
            style="@style/Theme.MaterialComponents.DayNight"
            android:id="@+id/materialCardView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/name_textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{item.name}"
            android:textSize="30dp"
            app:layout_constraintHorizontal_bias="0.212"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/options_recycle_view"
            adapter="@{adapter}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/name_textView"
            app:layout_constraintTop_toTopOf="@+id/materialCardView" />


    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

App theme is referenced in the manifest. And the style for the whole app is only in the app module, the Menu module doesn't have its style.xml file. The app contains the only activity of the project. I tried to find others having the same issue but no luck.

1 Answers

FragmentScenario API allows you to explicitly set desired theme for fragment container.

Having an application theme defined in styles.xml as AppTheme with attribute parent=Theme.MaterialComponents, a possible MenuFragment launch invocation would be as follows:

FragmentScenario.launchInContainer(MenuFragment.class, null, R.style.AppTheme, null);

Refer to FragmentScenario documentation for an explanation of the rest of the arguments that are outside of the scope of this answer.

Related