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
AppCompat theme as the base for my app there. So let’s take a look at that:
This theme does have a
MaterialComponents theme as its parent.
Thus began my noggin scratching.
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
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.
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:
for 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.
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.