Skip to content

View Recycling

Nathanael Silverman edited this page Aug 18, 2018 · 2 revisions

Paris makes it easy to recycle views even when their style changes. For example, here's a custom view used in the Airbnb application with two different styles applied:

The same view instance can be used in both cases, thus improving performance. There is one caveat however: both styles must declare the same attributes, otherwise recycling the view might have undesirable results.

Let's take the following style declarations as an example of what not to do:

<style name="Green">
    <item name="android:background">@color/green</item>
    <item name="actionStyle">...</item>
    <item name="secondaryActionStyle">...</item>
</style>

<style name="Purple">
    // No background attribute declared. Bad!
    <item name="actionStyle">...</item>
    <item name="secondaryActionStyle">...</item>
</style>

The Purple style doesn't declare a background. Assuming the parent's background is white this would normally be fine, except if the view can be recycled from a different style.

Say the view is first displayed with the Purple style, then recycled and restyled to Green:

The action styles are applied correctly, so is the background, and everything looks fine. Now let's take a look at the reverse scenario where Green is applied first, then Purple:

Purple doesn't declare a background, so the one from the Green style remains and the view looks completely wrong. Avoiding this is simple, Purple needs to declare a background as well:

<style name="Purple">
    <item name="android:background">@color/white</item>
    <item name="actionStyle">...</item>
    <item name="secondaryActionStyle">...</item>
</style>

Now views can be recycled back and forth between both styles:

The same principle holds true no matter the number of styles. If they are to be applied to the same views, they must declare the same attributes. Ordinarily this would be near-impossible to verify, thankfully Paris provides a utility to do just that.

Same Attributes Assertion

First, make sure the styles that can be applied to recyclable views have been linked to those views' classes using the @Style annotation:

@Styleable
class MyView(...) : View(...) {

    ...

    companion object {

        @Style
        val PURPLE_STYLE = R.style.MyView_Purple

        @Style
        val GREEN_STYLE = R.style.MyView_Green
    }
}
Click to see the example in Java.
@Styleable
public class MyView extends View {

    @Style
    static final int PURPLE_STYLE = R.style.MyView_Purple;

    @Style
    static final int GREEN_STYLE = R.style.MyView_Green;

    ...
}

For more on the subject, see Linking Styles to Custom Views.

Next, to ensure that your application's styleable views can be safely recycled, simply call Paris.assertStylesContainSameAttributes(context) from the launcher activity's onCreate method:

// Whichever activity is the entry to the application
class LauncherActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle) {
        ...

        if (BuildConfig.DEBUG) {
            // The check can run asynchronously to minimize impact
            AsyncTask.THREAD_POOL_EXECUTOR.execute {
                // Will throw in case of failure
                Paris.assertStylesContainSameAttributes(this)
            }
        }
    }
}

During development the application will crash on start if the styles are unsafe to recycle. An AssertionError will be thrown with a message detailing the problematic styles and attributes:

The MyView style "Purple" is missing the following attributes:
✕ android:background
It must declare the same attributes as the following styles:
Green

Thus making it easy to identify and fix the issues. Once all the linked styles declare the same attributes as those linked to the same views, the assert call will have no effect and the application will run normally.

@Null Values

Sometimes you might need to set certain attributes to null. In our previous examples if we had wanted the Purple style to remove the background entirely rather than set it to white we might have been tempted to do this:

<style name="Purple">
    <item name="android:background">@null</item>
    <item name="actionStyle">...</item>
    <item name="secondaryActionStyle">...</item>
</style>

Unfortunately, due to the way Android processes attributes, using @null as a value can't be supported by Paris. Instead you can use the following substitutes:

  • @anim/null_
  • @color/null_
  • @drawable/null_