Android Resources Refactoring

 

New Features in Realm Java

We recently released version 3.4 of Realm Java today, and with it introduced reverse relationship queries and sync progress listeners. Read on for all the details!

Introduction

I’m Yusuke Konishi, and I work at Quipper Limited. I sometimes create private projects, including the DroidKaigi 2017 official conference app. That app is a sample of this session.

Why Refactoring?

I joined Quipper Limited last June, and I helped change their design two times in eight months. It was a hard task because the resources were lawless: some text was written out directly, there were so many colors and styles… Also, the structure of our app makes resources more complex.

We have two products for many countries: StudySapuri for Japan, and Quipper for Global (Indonesia, Philippine, and Mexico). They are the same code, but divided by product flavor. Some features are different but almost appear the same. When I joined Quipper, there were different similar styles XML and complex colors XML. It was a hard task to refactor. After some trial and error, I found the best way to manage the resources. Today, I want to talk about how I refactored.

After this session maybe you can imagine how you can refactor resources in your app, and judge if you have to refactor resources or not.

Problems Of Complex Resources

In the case of our app, there were many similar tables and styles, no naming rules, and many colors and dimens were written directly. These complex resources made development speed slower and made it hard to keep unification of the design. It’s better to keep resources clean.

Refactoring Steps

First, I modified colors.xml and dimens.xml because they are the most primitive resources in Android. Styles and drawables depend on them (as I discovered later). Next, I extracted themes.xml from styles.xml, then created and applied a new styles.xml. Finally, I changed drawable names, such as icons, images, and selectors.

Styles Depend On Colors & Dimens

There is a dependency between styles, and colors, and dimens.

konishi-styles

For example, this text caption style depends on grey colors in colors.xml and 12 dp in 1.3 line spacing in dimens.xml. When you refactor resources, it’s better to start to modify colors and dimens first.

1. Colors

In my opinion, colors.xml should be like a color palette. It defines all the colors that are used in our app. If there are designers in your team, it is better to design colors with them.

This is our current colors.xml. I divided it into two sections: Greyscales and Quipper colors. It is not good to make too many sections; it is enough to divide simple sections.

konishi-colors

In the greyscales section, white, black, and grey colors are defined - the naming rule is important. I decided on the color names “grey1”, “grey2”, and “grey3” with our designers. In my opinion, the names “grey darker” and “grey lighter” is not good, because in some cases we have various grey colors and it is more difficult to understand the color label from the name “grey white”, “grey whiter”, or “grey whitest”. For transparent colors it’s better to name them like “color alpha percent” than “color transparent” (because like with the grey colors, we may have several transparent colors).

The second section defines the specific colors within our app. If there are no designers on your team, any names can be okay. In the DroidKaigi app, I used simple color names such as blue, red, green. If your team has designers, decide the colors with them because these color names become the common palette between engineers and designers.

These are the colors as defined in our design guidelines:

konishi-designer-colors

I used the same names in colors.xml. For example, “warning” and “danger”. It makes it easier to understand when we create new layouts from the design.

Refactoring Review

I showed you the refactoring steps of colors.xml. First, I created colors_new.xml, which defines ideal colors. Then I sent a pull request (PR) just for creating the new colors XML file. The important thing is to send a small PR for each step, to prevent conflicts and reduce a code reviewer’s cost.

The second refactoring step was to replace all colors written in the layout directly. (Unfortunately, we have a lot of these colors.)

Third, I changed all the old colors to the new colors step by step. For this step, it is also important to send small pull requests. At first, I replaced only black colors and sent a PR; next I replaced “grey1” colors and sent a PR; do it step by step.

We can Grep colors easily in Android Studio. Open the files in path dialog and Grep with the expression, and search and replace each expression. It’s not a smart way but it works well for us.

Finally, I removed all old colors.xml and renamed colors_new.xml. These are not difficult tasks. The only advice is to send small PRs if there are many engineers in your team.

2. dimens.xml

After modifying colors, the next target is dimens. Again, colors and dimens are primitive resources. It is better to define two files: dimens_base.xml and dimens.xml. dimens_base.xml defines basic dimens such as icon, text, and button. In other words, these dimens are for common components, not specific pages. On the other hand, dimens.xml defines dimens for specific pages.

This is our dimens_base.xml:

konishi-dimens base

I divided some sections by components. I wanted to name space feature for the resources, but in Android there is no such feature. So I divided sections by simple comments and named prefix.

I created some sections like space, radius, elevation, etc. There is no strict naming rule, just prefix like space_16dp and text_14dp. Some people may think these concrete size should not be included in the dimens’ name. That is a good point, but if we name these concrete sizes in dimens, we may have to do big changes when I want to change the design in the future. On the other hand, if we use a relative name, like “space medium” or “space large”, it may be enough to change only dimens.xml.

Relative naming, like “padding medium” or “margin large”, is difficult to use. The design specifies a concrete size. We have to remember its concrete size. But our team members often forget that name and go to dimens.xml to check the concrete size many times.

This is our dimens.xml:

konishi-dimens-xml

dimens.xml defines specific sizes by each formatting function or page. It will be added to and changed frequently and becomes large. That’s why I divided dimens_base.xml and dimens.xml.

For example, this is a login page on our app:

konishi-login

Login email and form size is specific for our login page. These sizes should be defined in dimens.xml. If the size is used for only one place, I define it in dimens.xml. In my opinion, it’s more simple to define all dimens.

When I refactored dimens, I created dimens_base.xml at first and replaced all dimens in each section. I replaced only space size at first. Similar to colors, I grepped and replaced and sent small pull requests. Sometimes, there were strange sizes not defined in the new dimens_base.xml. In these cases, I asked the designers. Strictly speaking, this is not refactoring because I changed the design.

This is not a difficult task, it’s just a preparation to refactor the themes and styles XML.

3. themes.xml

After refactoring colors and dimens, the next targets are themes and styles. We have to refactor more carefully because themes and styles affect many places.

We can set themes to application and activities in AndroidManifest.xml. A theme can apply common appearance, such as window background and colors and size of each component. For example, this Splash activity used a transparent theme to make the background color transparent.

By using theme, we can remove the background color in each layout. Before refactoring, some background colors were set in each activity or fragment. In most cases, this background can be removed. We can set window background after; it’s not necessary to set background color in each activity or fragment. Perhaps this is better in the performance viewpoint.

Besides the window background, theme can set various common styles. For example, if you set colorAccent, colorPrimary, and colorPrimaryDark, the color will be applied like this:

konishi-theme1 konishi-theme2

colorPrimaryDark is applied to the status bar, colorPrimary is applied to the active check box, and textColorSecondary is applied to the active check box. We can also set this theme for each component in layout. It’s difficult to understand which theme attributes can affect the component colors, but we can reduce unnecessary styles in each layout by using theme well.

If you want to change action bar colors, you can set another theme for the action bar. You don’t have to set theme in each activity or all components. If you want to change common styles of back button or text, it’s better to set each theme in themes.xml first.

konishi-action-bar

I want to explain why I divided themes.xml from the styles.xml file. themes.xml has almost the same structure as styles.xml, but styles.xml tends to be updated more frequently than themes.xml. In my opinion, the resources file should be divided according to the update frequency. That’s why I extracted themes.xml from styles.xml. Also, in the case of our app, there are complex product flavors - styles and themes were also complex.

We manage two apps which use the same code and are divided by product flavor: “Sapuri” for Japan and “Quipper” for Global. The appearance is almost the same, but there were two styles. There were both themes and styles in two different styles.xml. Almost all themes in each styles.xml were the same and should have been integrated. First, I extracted some themes from styles.xml. I wanted to refactor the styles at the same time, but I endured because the refactoring tasks should be done step-by-step.

themes.xml in the Sapuri flavored app should had been removed, because it’s almost the same as the main themes.xml and the only difference is colors. If I divided colors.xml properly, themes.xml could be integrated. Then, I checked themes one-by-one and removed them from themes.xml in Sapuri flavor. After all themes were integrated, finally, I removed themes.xml from Sapuri flavor.

That’s all for theme refactoring steps, but the change of themes affects many places and themes behavior depends on API level. I was afraid to cross layouts, so I modified the GitHub Pull Request template to check.

Pull Request Template

Our default GitHub Pull Request (PR) template only has a few sections, because too many sections makes it difficult to fill out. If some sections are not necessary, it’s okay to remove them.

In the first “overview” section, we write a simple description. In the next “known issues” section, we write an issue that has been already known. In the “design review” section, we ask the designers to check the layout. Basically, we match the PR after the designers’ review. In the last “screenshot” section, we put screenshot before and after the changes, or the screenshot for each API level.

This is an example of a PR when I extracted themes.xml and applied window background colors. I described the summary of every section. In this case, added the background color, which is defined in design guideline, and extracted themes.xml from styles.xml. In the “known issues” section, I mention some layout issues caused by this PR. For example, one issue is a strange progress icon color on toolbar. I fixed it by creating another theme for the progress bar on another PR.

konishi-pr-exmaple.png

Finally, in the “screenshot” section, I put the screenshot of API 16 and API 20 because themes.xml behavior depends on API level. If you have some UI test to take screenshots, it becomes easier to check.

4. styles.xml

Styles can define layout attributes and make our layout more simple. But styles.xml tends to be complex.

After some trial and error, I decided to split styles.xml into a few different files. I created other styles for specific pages, e.g., styles_login.xml or styles_messages.xml. styles.xml defines basic styles like icon, text, and button. This file is not changed frequently (I would change these styles if necessary). On the other hand, I created other styles XML files for each page. This file does not have strict role, it’s enough to keep the suffix of the file name and the prefix of the attribute name. All members can make new styles XML files if necessary.

As for styles.xml, we can make the layout simple:

konishi-styles

This example is “button”; all buttons have a common design. It’s nice to create styles to keep a unified design. The button style is like this:

konishi-button-style

Some layouts are undefined. The last attribute is a state list animator to remove button shadow. In fact, our designers asked me to remove the button shadow. If we didn’t apply this button style, it might be hard to change all button styles, but actually, I only added state list animator attributes to the button style.

We can inherit styles by connecting with “dot”. If you want another color button style, you can create Button.Primary and define only background and text attributes. I like this because it’s easy to use styles with auto-complete in Android Studio. When I type Button., Android Studio suggests Button.Primary.

Some say that layout attributes like layout_height or layout_width should not be included in styles.xml, and that’s a good point. It’s not good to include some layout attributes like layout_margin, but it’s not a problem to include layout_height or layout_width in styles.xml. Actually, we haven’t faced any problems yet.

Toolbar Style

I’ll show you another example: toolbar style.

konishi-toolbar-style

This theme inherits app contact toolbar style by using parent attributes. Besides toolbar style, Widget.AppCompat has some styles for material design components such as text or check box. It’s better to look for existing styles before you create new styles.

On the other hand, I created some style files for specific pages. For example, styles_settings.xml defines a header text style that’s only used on the settings page. If a style is used in multiple pages, it’s better to define it in styles.xml. This is a special text title, so I created a new file, styles_settings.xml.

By dividing the styles file, we can keep styles.xml small and clean, and we can find different styles easily.

5. drawables

Finally, I refactored drawables. Generally, it’s difficult to manage drawables because there tend to be many of them in an app.

The easy way to manage drawables is to decide a name prefix. Icon name should include its “shape”, not “components”. For example, if it’s a video play icon, the name ic_play is better than ic_video_play because this icon may be used as a sound play icon. Of course, I can create both video and sound play icons, but I want to reduce the number of drawable files, so I use the same icon.

<ImageView
  style="@style/Icon.Small"
  android:tint="@color/grey2"
  app:srcCompat="@drawable/vec_ic_delete_22"/>

I didn’t include the colors in the name because I applied tint to the icons. Image view has tint attributes. But if you use color state list, this tint attribute doesn’t work well - so I created this static method. If you use color state list in some places, maybe it’s better to create data binding adapter to specify it in layout.xml.

public static void setImageTintCompat(@NonNull ImageView imageView, @ColorRes int ColorResId) {
  ColorStateList colorStateList = ContextCompat.getColorStateList(
    ImageView.getContext(), ColorResId);
  if (colorStateList != null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      imageView.setImageTintList(colorStateList);
    } else {
      Drawable src = imageView.getDrawable();
      if (src != null) {
        Drawable drawable = DrawableCompat.wrap(src);
        DrawableCompat.setTintList(drawble, colorStateList);
        imageView.setImageDrawable(drawble);
      }
    }
  }
}

konishi-naming-rules

Also, I decided a naming rule not only for icons but vector icons or images.

In general, refactoring drawables is not difficult, just a bother. First, I removed unused drawables. There is a “shrink” option so the APK file doesn’t contain unused drawables, but if you don’t do this at first, you may spend time to rename unused icons. Unused drawables should be removed first.

You can check for unused images from the Analyze menu of Android Studio, but you have to be careful because when you remove the icon, the function is not stable if you used images by data binding.

The next step is renaming all icon names. I created the icons name list on spreadsheet and replaced all at once. In my team, icons derive from a Sketch file and are converted to vector drawables automatically in CI process. All I did was to rename the icon in the Sketch file.

After changing icons, next are the image names. Again, the refactoring should be done step-by-step. The last step is to rename other drawables, little-by-little.

Summary

1. Refactor colors and dimens at first. The resources have dependencies between styles and colors and dimens. It is better to refactor colors and dimens first.

2. Refactor step-by-step to prevent conflicts, but also to reduce reviewers’ cost.

3. Naming rules and file division policy are important. Android resources management system is not so good. We should divide resources file and make name rules for good management.


Yusuke Konishi

Yusuke Konishi

Quipper Limited engineer and longtime Doraemon fan.

Transcribed by Sandra Sanchez-Roige
Edited by Curtis Chen