Refactoring Android Themes with Style

Preventing regressions with custom Lint checks (Part 5)

Ataul Munim
3 min readJul 17, 2020

This is part five of a five-part series on refactoring themes and styles — it’ll make sense without reading the other parts, but if you want the full picture, start reading here:

After all the hard work we’ve put in, detangling our themes, defining new attributes, and determining default styles, it’d be a shame if now someone were to… hardcode a color 👀

It’s going to happen. It’s ok. What we’re after is something like this, where the rate at which hardcoded colors are used should plateau, and then start to go down:

At Monzo, we wrote custom Android Lint checks to help us avoid creating regressions with themes and styles.

“This could be a Lint check”

We have a custom Slack emoji called :this-could-be-a-lint-check: which is just a picture of Tor Norbye’s face, applied whenever we describe a convention that could be enforced with Lint.

Writing a good Lint check is difficult. We need to avoid false-positives (misclassifying acceptable code as an error) and false-negatives (failing to identify a violation).

Luckily for us, we don’t have to write good Lint checks, we just need them to be good enough!

In part one of this series, we described a very particular naming convention for themes vs. styles, where style resources beginning with Theme is a theme and Widget is a [view] style. Accepting this as an axiom means that we can begin to write some simple Lint checks; while this isn’t going to be true for all projects, it is true for ours, hence “good enough”.

A “good enough” check

When we established the difference between styles and themes, we wrote a simple rule to mitigate the usage of themes as styles.

It looks in the layout resource directory for XML elements that have the attribute style specified (i.e. view declarations with style). It checks the value for this attribute, and if it contains Theme, then that’s a paddlin’!

They can get more complicated, but even the more involved ones aren’t very clever. This reports violations of hardcoded colors in XML:

Reducing the effort required to write checks

Writing a Lint check can feel a bit daunting. We tried to mitigate this by writing some convenience functions to reduce the required boilerplate.

It comes at a cost: each check is less specific about why a violation occurs. This is “good enough” for us since we just want to identify violations and then fail the build.

We have some domain-specific extension functions too which are internal to our Lint module:

We can reuse these ones between a few different checks, and they make the checks themselves a bit easier to read.

Hasten slowly

Adding rules over time, rather than all at once, has the added benefit that the team can slowly get used to them. Over the last few months, we’ve added a bunch:

It doesn’t capture every rule that we have and it doesn’t need to; each one helps a little bit.

What’s next?

If you want to learn more about Lint checks, I’d recommend watching these two presentations from last year:

Alex has a really helpful repository called “android-lint-checks-demo” which an excellent reference, so check that out too.

This is the last post in the Refactoring Themes with Style series, thanks for making it this far! If you have any questions or comments, let me know below or reach out on Twitter:

--

--

Ataul Munim

Android Developer Relations Engineer, focusing on Wear OS.