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.
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.
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:
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
:
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
:
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:
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:
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.
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.
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:
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:
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.
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);
}
}
}
}
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.
Receive news and updates from Realm straight to your inbox