When is a Material theme not a Material theme?
That may sound like the first line of a joke, but it is actually a real problem I faced recently. It caused me much noggin scratching until I chanced upon the cause. And you’ll have to wait until the end of the article for the punch line!
encountered the issue when putting together the code for the recent Currency Converter series. I have created a separate project which highlights the issue as the Currency Converter project is relatively large.
There are two separate branches in the sample code. The broken branch is, as its name suggests, the broken version. It compiles perfectly but crashes during the inflation of the main layout. For anyone wanting to play along at home, you can check out the broken branch and try and work out where the problem lies before reading further.
Investigating the problem
For those that haven’t read the previous articles, the project in question had little in the way of layouts, but I wanted to add a button to the main layout so that I could test triggering an immediate data refresh. All was working fine until I added a Button
to the layout whereupon I started getting runtime crashes.
Investigating the runtime exception is the obvious place to start:
com.stylingandroid.brokenmaterialtheme E/AndroidRuntime: Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.AppCompat (or a descendant). at com.google.android.material.internal.ThemeEnforcement.checkTheme(ThemeEnforcement.java:243) at com.google.android.material.internal.ThemeEnforcement.checkAppCompatTheme(ThemeEnforcement.java:213) at com.google.android.material.internal.ThemeEnforcement.checkCompatibleTheme(ThemeEnforcement.java:148) at com.google.android.material.internal.ThemeEnforcement.obtainStyledAttributes(ThemeEnforcement.java:76) at com.google.android.material.button.MaterialButton.(MaterialButton.java:229) at com.google.android.material.button.MaterialButton. (MaterialButton.java:220) ... 27 more
I haven’t reproduced the entire stack trace here, just the most relevant part. The main error is:
java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.AppCompat (or a descendant).
So this seems fairly straightforward – I forgot to use a Material
or AppCompat
theme as the base for my app there. So let’s take a look at that:
Wait. Wat?
This theme does have a MaterialComponents
theme as its parent.
Thus began my noggin scratching.
Digging Deeper
I confirmed that my dark theme also used a MaterialComponents
theme as its parent. It did.
I confirmed that the MaterialComponents
theme actually used an AppCompat
theme as its parent. It did.
I verified that the theme being used to perform inflation was, in fact, the app theme. It was. This actually involved some rather hack, convoluted code.
At this point I started analysing how MDC works internally. The fact that my standard Button
was being transformed to a MaterialButton
showed that MDC inflation was happening. The stack trace from earlier shows that it was in the MaterialButton
constructor that the theme validation was taking place.
An internal MDC class named ThemeEnforcement was performing and failing the theme validation. The specific test that was failing is checkAppCompatTheme()
. That method is being called from checkCompatibleTheme()
The really odd thing is that checkAppCompatTheme()
is the final line in checkCompatibleTheme()
. There are other checks which all pass prior to this one. One of these is checkMaterialTheme()
. I stepped through the code using a debugger and verified that these checks were all performed and passed.
So, the Material theme check passed whereas the AppCompt theme check failed. And the MaterialComponents
theme that I was using for my app theme extends an AppCompat
theme.
Noggin scratching continued
Deeper Down the rabbit hole
A deep dive in to understanding precisely what checkAppCompatTheme()
does was required. In fact it’s really quite simple – it checks the theme attribute colorPrimary
has been set. Nothing more than that.
So I went back to my theme (as posted earlier in the article) and confirmed that colorPrimary
was being set in the theme. It was.
My noggin was becoming raw from all the scratching.
Eureka!
My “Eureka!” moment came from something I spotted out of the corner of my eye. I was checking my colors.xml
file, and wasn’t seeing any obvious issues:
#000000 #FFFFFF #t6565575 #156912 #4EE147
However, I spotted something odd in Android Studio:
There’s no wiggly red line showing an error in the code itself, but the colour swatch in the left gutter is missing for colorPrimary
. All of the other colour definitions have colour swatches, except colorPrimary
. Looking closer at this, it is clear that #t6565575
is not a valid hex colour value. But it’s difficult to spot. Moreover, this was compiling correctly.
I’m not quite sure how this got changed from the correct value of #1E9618
. I must have pasted something without realising that this window was active at some point. The odd thing is why AAPT didn’t complain about it. If I just insert a random t
in the correct value (#t1E9618
), then AAPT fails:
/app/src/main/res/values/colors.xml:6:4: Invalidfor given resource value.
But, for some reason, the specific value of #t6565575
compiles. I checked the resource definitions that were added to the APK and found that saGreen
was defined with an empty value. This now explains why the AppCompat
theme check was failing – because there was not valid value for colorPrimary
. But it’s still something of a mystery why this value did not produce an error from AAPT.
Conclusion
A perfect combination of factors produced this issue. First there was an invalid colour value which somehow did not cause AAPT to barf. This resulted in a null value for saGreen
. This happened to be used for colorPrimary
. Any other usage of it would not have caused the ThemeEnforcement
checks to pass.
While the chances of anyone else hitting this exact same problem are small, I though that sharing the steps I went through to try and understand what was happening might help others trying to track down similarly obscure issues.
To give the punch line to the question at the beginning:
Q: When is a Material theme not a Material theme?
A: When it has a null colorPrimary.
What a rubbish joke that is!
The fixed source code for this article is available here.
© 2021, Mark Allison. All rights reserved.
Copyright © 2021 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
Wow.!!!! Thanks!I had the same problem and your solution helped alot!! Thanks!!