Tokopedia Android App Journey to Support Dark Mode

Yehezkiel L
Tokopedia Engineering
8 min readMay 20, 2022

co-authored by IJ

Android 10 has included the Dark Theme to its devices a while ago, and its adopters are rising quickly over time. Dark Mode has many benefits, such as reduced battery usage, improved visibility, reduced eye strain, etc.

This article will share and explain Tokopedia team’s journey to convert our Android App to support Dark Mode. It will be bundled with some technical implementation as well.

Opening

We know that users have a massive demand for the Dark Mode feature. Well, the wait is over!!, Tokopedia has officially published its support for Dark Mode in the Beta phase. We’ve spent a lot of time and effort converting our Tokopedia App, which consists of hundreds of modules to accommodate dark mode support. There were lots of problems that we encountered in our quest. I will share all of them in this article and the solution.

Bill Page

Prerequisite

  • Basic Knowledge of Dark Mode

It would be best to have basic knowledge about how to make an application that supports dark mode. Therefore, this article will only share major hurdles in converting our application. For the official documentation about dark themes, please refer to this link.

  • Color Helper Library

We also have an internal library for storing and managing color. This library is only a module that contains colors.xml with values-night resources. We recommend using this approach. So the color in your application is centralized in one place, and less effort is needed to do any maintenance.

  • Force App to Light Mode

While we were in the development phase, we applied the code that forced the app to light mode only. Force to light mode will prevent the app from using dark mode in production while developers are still in the development phase.

Imagine if we have ten pages and only two of them already supports the dark mode and the device theme of the users is in the dark theme, then the users will have a broken experience with dark and light mode mixed up in the app. If we don’t force the app to light mode, the app will follow the device system theme.

Use the below code to force your app to light mode (place it in your application class).

AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_NO);

Finally, after all of the pages support dark mode. Then we can start using the default system theme and begin to remove the code that we created to force to light mode.

How do we convert hundreds of modules to Dark Mode with as little effort as possible?

Since we have hundreds of modules owned by several teams, the manual approach is not feasible and requires too much effort. The manual approach means we have to change the colors one by one, create night res folders for colors, or even check and change the theme. Those could take forever to convert and the chance of making a mistake is high.

Our solution is to create an automation script to convert each particular module to dark mode.

Automation Script

The script is created in python. Basically, it will traverse to the selected module and scan all of the files that generally contain colors such as .kt, .java, .xml. The script will ignore files with types aside from the ones mentioned before.

If the script finds the type of file, for example, ProductFragment.kt, it will open the file and read lines one by one to search for any occurrence of color.

for line in fileinput.input({path_to_product_fragment}, inplace=1):
# find if line contains color with regex

Regex is used to get each color in each particular step because each step has different color target to be replaced. This regex for example :

'com\.[\w_]+\.[\w_]+\.R\.color\.[\w_]+|R\.color\.\w+|\.color\.\w+|@color/\w+|android.R.color.\w+|@android:color/\w+

After a color is found in a line. The script will check whether the color is available on our dark mode library. If it’s available, then the color needs to be converted to the correct color. Then the script will get the certain color that matches and replace it. Example of library :

Old Color, Dark Mode ColorR.color.Blue_B100,R.color.Unify_B100
@color/Blue_B100,@color/Unify_B100
R.color.Blue_B200,R.color.Unify_B200
@color/Blue_B200,@color/Unify_B200
R.color.Blue_B300,R.color.Unify_B300
@color/Blue_B300,@color/Unify_B300
R.color.Blue_B400,R.color.Unify_B400

For instance, in this line of xml file, we get the color as @color/Blue_B100 and the script will change it based on the color library.

android:textColor="@color/Blue_B100" <- before convertedandroid:textColor="@color/Unify_B100" <- after converted

How it works?

The high level flow can be presented in this flow chart:

Simplify Flow

In Tokopedia we’re doing three steps to completely convert all colors to dark mode support :

  1. Convert all existing colors from the library that are not supported as the dark mode.
    As we mentioned before, We have an internal library that centralized our color. Unfortunately, the previous colors that we used are not supported as a dark mode because those do not have night value. So we run the script as we describe above.
  2. Convert all custom colors into our internal colors.
    In Tokopedia we have hundreds of modules, each module has its custom colors (usually located at res -> values -> colors.xml). It’s another problem if we want to convert it to dark mode support. So the script will do :
    - Get all of the custom colors in our source code then place it in one place.
    - Get the hex color of each and find the nearest similar color to our color library. The nearest color script will return the correct closest color result.
    - Change the color to the result and change the color in class.
  3. Convert all hard coded colors into our internal colors.
    In this case, we will cater the hard coded hex color in our class, for example, android:background=”#123456". The approach is fairly similar to the first one :
    1. Loop all of the android classes
    2. Find hex color by using this regex r’#([0–9a-fA-F]{3}|[0–9a-fA-F]{6}|[0–9a-fA-F]{8})\b’
    3. Get the hex color and pass into nearest color picker script. The script will return our correct library color. Example : #123213 will turn into @color/Unify_Black
    4. Change the color in class to the result correct color.

Other Tool

  • Nearest Color Script. You can create your own algorithm to find the closest color to color, by giving the list of colors. By using KNN Algorithm, python already has a library to find the nearest neighbor by using scipy, example click here.
    Turn your library color and your input color to RGB . Submit to the scipy and get the nearest RGB in the list.
  • Theme Changer Script. This script would change our existing theme to theme that recommended by google to dark mode support (Theme.AppCompat.DayNight).
  • Dark Mode Coverage Dashboard
    To maintain dark mode coverage in our app and ensure all of the new codes are ready for dark mode, We’ve created a dashboard to track whether the modules are ready or not. This tool is also linked to Pull Request Checker in our Jenkins.
    If there is new pull request that detected with less than 100% coverage, then the PR will be block and developer’s feature can’t be merge to our master branch.

Best Practice

  • Prevent creating theme styles with the same name; the exact name can cause dark mode not to work.
  • If using CardView, use app:cardBackgroundColor instead of android:background to apply dark mode color.
  • Use a custom color that doesn’t have any night-valuesto the color you don’t want to change in dark mode.
  • Beware of strings.xml; always check this class because the color resource is possible located in strings.xml file.
  • Avoid color reference (@color/blue_night) in .xml that contains vector; just hardcoded the color with hex (#003c94) to prevent crash in lower os.
  • Create a unique identifier to prevent the script to keep your intended custom color. So whenever the script is re-run, your color will not be converted. For example, I’m using _dms_ (dark mode support) as an identifier :
    <color name=”product_detail_dms_tradein_blue”>#32afff</color>
  • If your app contains webview, we also need to support it to dark mode. First thing first, the web itself should support dark mode. Then to tell the web whether we need to use dark mode or not is, the app needs to send some additional flag to webview from the header. Google also has an approach for this check here.
  • In case to keep the dark mode experience always good and on track, we need some prevention and automation to block the feature that not using dark mode color. Usually, we block the Github pull request auto merge and wait till developer fix with the correct color.

Result

These are some of the results of dark mode support in our Tokopedia customer app.

Home Page
Product Page
Chat Page

That’s pretty much how we convert our app to dark mode. Automation helps us a lot and reduces human errors. But we still have to continuously improve our dark mode experience because some of the cases need to be manually converted and are not being catered in the script.

As always, we have an opening at Tokopedia. We are an Indonesian technology company with a mission to democratize commerce through technology and help everyone achieve more. Find your Dream Job with us in Tokopedia!
https://www.tokopedia.com/careers/

--

--