When undertaking a project one must balance between the upfront development cost and long term cost to maintain the project. The reduced upfront cost of a solution with a short development time may end up with a significantly higher overall cost as maintaining the solution, both in terms of feature support and technical debt, can force long term costs to balloon. This article explores the decisions, process and debts undertaken while implementing support for dark mode on the Android version of the LINE app.
Supporting dark mode
In September 2019, both Android 10 and iOS 13 were released, introducing support for dark mode. Only a few months earlier in July that year, the LINE client development team would embark on a project to add dark mode support to the LINE app on both platforms.
While various approaches to supporting this were considered, one of the major decisions made was that the project would roll out its first milestone shortly after Android and iOS had released support. This meant that there was only approximately 3 months to get an initial release that supported dark mode. For an app such as LINE, with many screens and components that needed migrating (including a lot of "legacy" features that no longer had an owner) this task might have seemed impossible for that timeframe. However, there was a solution that allowed for a much faster rollout: LINE Themes.
Before getting into details here, it should be pointed out that LINE's themes and Android's themes are completely unrelated. While Android's theming can be part of a solution for an app to implement dark mode, this article is not concerned with it and any reference to theming on the Android client is specifically referring to LINE's theming.
LINE has supported theming for more than a decade, allowing users to customize their UI's appearance by simply downloading a theme from the LINE Shop. This theme data is then used by the client to set colors and images of the UI components. What isn't apparent to users is that iOS and Android have differing implementations of how this theme is applied.
While there are a number of technical differences between theming for each platform, the main point of interest regarding support for dark mode is how the default theme is applied. On iOS, the default theme is implemented in the same way as any other theme; there is theme metadata packaged with the application with all the default values required to theme the application.
Android on the other hand has the default theme set in its layouts or code. From a technical perspective this means the default theme isn't actually a theme. When calls to apply a theme occur, nothing happens if the default theme is already selected.
These differences meant that a different approach would be taken on implementing dark mode on each platform. iOS would have its theme applicator updated to support semantic colors directly and have the default theme metadata updated with the semantic colors. For iOS, this approach made sense. Android obviously couldn't take the same approach and instead took an approach which would ultimately introduce a notable amount of technical debt and long term costs.
Already available to users was a theme simply called "Black". As one could imagine, this theme is a suitable substitute for a native dark mode implementation. Using this for dark mode meant that, in theory, the work required to migrate screens and components was already done. For Android devices, it was decided that this theme would be automatically applied when the user selected dark mode.
While this solution might seem fairly straight forward, there were a number of issues that needed addressing to support this behavior.
LINE Android's theming hurdles
The first major issue is that LINE Android’s theme management code doesn't support seamlessly switching between themes. The biggest hurdle to overcome was the fact that the theme management code wasn't written to support multiple themes being installed at once. Astute readers may be asking "You said the default theme wasn't a theme, why is this an issue?" Internally, LINE Android’s theme management doesn't see the default theme as a special case. When the default theme is in use, it is treated as a theme with no data and hence applies nothing. To address this, a solution was required that would minimize the impact on the user experience, all without requiring long development times.
The simplest solution would have been to install the theme each time the user changed to and from dark mode. However, this would negatively impact the user experience with slow UI responsiveness in the app.
Perhaps the correct solution would have been a major refactor to add support for multiple themes. Unfortunately this would likely have taken longer than the 3 months allocated, and ultimately the plan was to support native semantic colors anyway thus no longer requiring such a feature.
Instead, the final solution was to hack in support for "light" and "dark" mode themes. The light theme would behave exactly the same as theming always had and the dark theme would always be the Black theme. These two themes would be installed at the same time and had reduced overheads switching between them. Unfortunately, with this solution the code became complex with numerous conditional branches and specialized methods.
At this point we can already see the technical debt building within the app, and we were yet to solve the entire problem.
Internal and external costs
The other issue was how to get the Black theme installed in the app. Regardless of how this was to be approached there was a problem that needed to be considered. On top of needing to support devices with lower specifications, Android devices are primarily popular in regions where poor mobile network coverage, data caps and slow network speeds could also present problems.
With the above in mind, the decision was made to have the Black theme be optional. This would mean that it was not packaged with the application and would need to be downloaded separately after the user installed the application.
This decision has led to two on-going concerns. Obviously, allowing the theme to be optional now meant a new set of scenarios and edge cases needed to be supported. As with the changes required to support the theme management, this introduced further technical debt.
Outside of the app itself there was another concern; the increased server and network costs. As the theme was now a fundamental component of the application, it was downloaded when a user registers or logs into their account and updated as required. Despite the Black theme being one of the smallest themes that LINE supports, the server and network resources required to deliver this to millions of users is massive.
In the four years since the above was implemented, there have been numerous issues supporting it. The number of edge cases has been surprisingly large, bringing with them bugs and further technical debt.
Not only has this project significantly added to the code complexity, it has also increased the workload for QA whenever theme testing is required. With so many possible theme states, the number of scenarios QA needs to check has increased dramatically, requiring more time testing and/or more testers to cover all the possibilities.
Migration to semantic colors
With all the above considered, it might seem that in retrospect supporting native semantic colors for dark mode would have been the better approach. We started migration to support native semantic colors in early 2022, but it's still ongoing 18 months later.
Had this been undertaken from the start, this lengthy development time would have meant the Android version of LINE not being able to support dark mode for more than a year after introducing support to LINE iOS. However, it should be noted that if this was taken on as the original solution, the implementation time would likely have been much shorter.
On top of this, supporting migration has required additional code, once again increasing technical debt and requiring additional QA. The application also had numerous new features added, increasing the amount of work required.
In retrospect, the decision to go with the Black theme was likely the best option. However, there are two aspects that should have been done differently; the Black theme should have instead been bundled with the application, and migration to native semantic colors should have been started immediately after Black theme support was finished. This may have led to a minor inconvenience for some users for a period but overall this would have heavily reduced technical debt, QA testing, and would have externalized the server and network costs that have built up over the years.
It should be noted that implementing the short term and then long term solution may have the highest upfront costs, but in cases where the long term costs are compounded they will quickly exceed the increased upfront costs.
As one can see, the decision to go with a solution with a short development time can incur a large amount of future debt and costs. It is important to evaluate these during planning and consider if higher short term costs and user inconvenience is a good trade-off compared to long term technical debt and costs.
In cases where a short development period is being forced by external factors, early refactoring and migration to a proper long term solution will help minimize the problems caused and reduce the compounding effect they can have over time.