Search code examples
androidandroid-studiomaterial-designandroid-resourcesandroid-theme

How can I access theme color attributes via R.attr.colorPrimary


I'm using Android Studio Electric Eel and I started with a new "Basic Activity" project. It comes with a light and dark theme, /res/values/theme.xml /res/values-night/theme.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.Example" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <item name="colorPrimary">@color/md_theme_light_primary</item>
    </style>
</resources>

I understand I need to use this code:

val typedValue = TypedValue()
view.context.theme.resolveAttribute(R.attr.colorPrimary, typedValue, true)
val colorPrimary = typedValue.data

And that works almost. For whatever reason, R.attr.colorPrimary won't resolve off of the default com.example.project.R that is my local project. When I type R.attr.colorPrimary it only auto-complete resolves to com.google.android.material.R.attr.colorPrimary (it also can resolve to some androidx components, that also work)

When I use that, it does work, and it does use my colors.xml pointed to from my theme.xml. I just want to understand why it won't simply resolve from my theme.xml. In all the examples I found online it was named styles.xml so I renamed it to that, and everything worked identically, no change.

When I ctrl-click the resolved color it takes me to a cached file in ...\transformed\appcompat-1.6.1\res\values\values.xml that's <attr format="color" name="colorPrimary"/>

And if I ctrl-click the parent="Theme.MaterialComponents.DayNight.DarkActionBar" it takes me to a cache of ...\transformed\material-1.8.0\res\values\values.xml

I just want to understand why. It seems my colors still override the default ones and it works using the com.google.android.material.R. In xml layouts ?attr/colorPrimary works just fine. But in nowhere that I looked did it say that when programmatically resolving it needed that path.


Solution

  • And that works almost. For whatever reason, R.attr.colorPrimary won't resolve off of the default com.example.project.R that is my local project.

    That's because colorPrimary is not an attribute that you've defined in your local project. It's an attribute that is defined in the material library.

    When I use that, it does work, and it does use my colors.xml pointed to from my theme.xml. I just want to understand why it won't simply resolve from my theme.xml.

    An attribute doesn't "resolve from [your] theme.xml". An attribute is defined in an attrs.xml file just like colors or dimens. You've not defined one, hence it does not exist.

    When you use it in your themes.xml file as so:

    <resources xmlns:tools="http://schemas.android.com/tools">
        <style name="Theme.Example" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
            <item name="colorPrimary">@color/md_theme_light_primary</item>
        </style>
    </resources>
    

    You are extending the existing material theme (the parent attribute) and setting the colorPrimary attribute that is already defined in that theme. You are not declaring your own colorPrimary attribute. If you did not set the parent attribute to a theme that had colorPrimary, you would get an error.

    In all the examples I found online it was named styles.xml so I renamed it to that, and everything worked identically, no change.

    Right - the name of the file is irrelevant.

    When I ctrl-click the resolved color it takes me to a cached file in ...\transformed\appcompat-1.6.1\res\values\values.xml that's

    And if I ctrl-click the parent="Theme.MaterialComponents.DayNight.DarkActionBar" it takes me to a cache of ...\transformed\material-1.8.0\res\values\values.xml

    Android theming via XML is a complicated web of nested overrides. colorPrimary as a concept was introduced with Material design in the first "app compat" libraries. It's since been updated and enhanced with dedicated material libraries that extend the original and redefine the values to adhere to the latest Material Guidelines.

    I just want to understand why. It seems my colors still override the default ones and it works using the com.google.android.material.R. In xml layouts ?attr/colorPrimary works just fine. But in nowhere that I looked did it say that when programmatically resolving it needed that path.

    In summary, colorPrimary is a theme attribute defined by the appcompat and material design libraries, so it lives within their package.

    In XML, ?attr/colorPrimary references the current theme which in your case will be "Theme.Example", which you've declared to set colorPrimary to @color/md_theme_light_primary.

    In code, though, colorPrimary is just a constant and must be resolved to clarify which colorPrimary of all those that have been defined you're referring to. Ultimately, it doesn't really matter which package you use to access colorPrimary in code - its value will resolve to what you have set in your theme.