Kotlin
A concise multiplatform language developed by JetBrains
JetBrains Toolbox Case Study: Moving 1M users to Kotlin & Compose Multiplatform
Victor Kropp, the Team Lead for the Toolbox team at JetBrains, shares the story of adopting Kotlin and Compose Multiplatform on desktop.
The JetBrains Toolbox App is the single entry point for developing using JetBrains IDEs. It serves as a control panel for tools and projects, and makes installing and updating JetBrains IDEs quick and easy. Originally started in 2015 as a hackathon project, the application now serves one million monthly active users, and helps them be more productive with their JetBrains products.
Read on to understand how the Toolbox team moved their application from C++ and JavaScript to 100% Kotlin and Compose Multiplatform, and ended up making their code easier to maintain and work with while shipping smaller artifacts with better runtime performance.
To hear the story directly from Victor Kropp, who leads the Toolbox team at JetBrains, check out Talking Kotlin #107:
Victor, can you introduce the architecture and tech stack used by JetBrains Toolbox?
The Toolbox App is a typical client-server application. The desktop app requests a list of available tools from the server, shows it to the user, and downloads updates for JetBrains products when required. We implemented the server-side part of our application in Kotlin from the very beginning. The desktop application, however, was a different story.
When we started building the desktop app for JetBrains Toolbox back in 2015, we used C++ to implement its business logic and used the Chromium Embedded Framework together with React and HTML/CSS/JS to build the user interface. We made this choice at a time when Kotlin 1.0 hadn’t been released yet – and neither was the modular JDK, which came with Java 9. We couldn’t afford bundling a Java Runtime Environment (JRE) weighing hundreds of megabytes for our small helper application, and we didn’t want to trouble our users with having to manually set up their environment. So, we chose a completely different approach.
In 2021, we completed the final step of making the desktop application 100% Kotlin by migrating the user interface from React to Compose Multiplatform, more specifically Compose for Desktop.
Visit the Compose for Desktop website
How do you use Kotlin and its libraries in your product?
With the migration to Kotlin completed, we use it everywhere. Besides Compose for Desktop powering the user interface, we make heavy use of kotlinx.coroutines for all asynchronous jobs. The Toolbox App manipulates a lot of JSON objects, so we naturally use kotlinx.serialization for (de)serialization.
The server side is kept as simple as possible. In fact, it isn’t an HTTP Server as you might imagine it. All of the information and descriptions for installable tools (called “feeds” in Toolbox) are generated statically and served as JSON files from the CDN. They don’t change often, so we update them only when a new version of any tool is released as part of the continuous delivery pipeline on TeamCity. The generator is a simple command line Kotlin program that is invoked as a “build configuration” (TeamCity’s name for a job), which is automatically triggered on each build of every supported product. A second job then periodically merges all newly generated feeds, discards the outdated ones, and performs validation.
Why did the Toolbox team decide to use Kotlin for Desktop application development?
Before moving to Compose for Desktop, we used the Chromium Embedded Framework to build the user interface for the Toolbox App, and using native C++ for our business logic helped us support all major desktop operating systems easily. (Since then, many things have changed, and we made the decision to move all C++ business logic to Kotlin running on the JVM in 2020.)
Back in 2015, those were great choices to kickstart the project! We were able to reuse components from Ring UI, a library of web UI components built by JetBrains. We also already had a lot of previous experience in web development and working with React.
However, it had its disadvantages:
- The Chromium Embedded Framework is known for its resource consumption. Even when idle, JetBrains Toolbox would use at least 200 MiB of RAM. We also couldn’t unload the whole framework when the window was invisible, because it would result in a multi-second delay for our users when trying to interact with the app.
- We needed a full-blown client-server architecture inside a single desktop application. The embedded web UI and the business logic were written in different languages and by different people. This complicated the development process and required the resources to send megabytes of JSON back and forth inside the application, using up CPU resources on the (de)serialization of data that was already there.
After moving our application to 100% Kotlin, that situation has improved significantly:
- Compose for Desktop is now much less resource intensive. The Compose framework provides better runtime performance compared to our JavaScript implementation, and when running idly in the background, we managed to greatly reduce the RAM used by the app.
- Using a single language means that every developer can make a feature from start to finish without switching contexts. It is faster, less error prone, and improves knowledge sharing among developers. The entire application also uses the same representation for data in memory, skipping the need for extra (de)serialization steps.
Can you share your experience of introducing Kotlin to your Toolbox?
We faced many challenges. First of all, we needed to migrate a five year-old codebase, with all its features and quirks, to a different stack. To ensure that the core of our application works as intended, we migrated all of our unit tests. However, our application requires a lot of external dependencies, which obviously vary in different ecosystems. Some things didn’t work in the previous implementation and started working in the new one without any action from our side, simply because the new dependency supported them. However, other things we took for granted stopped working. In some cases, we didn’t know about those differences until after a public release. Examples for both categories are the different aspects of operating system integration, like the system tray (menubar) icon, or proxy servers and SSL certificates. On the other hand, it also allowed us to reuse Kotlin code written by other teams at JetBrains, like reusing the code powering IntelliJ IDEA’s project search in the Toolbox’s “Projects” tab or detecting specific enterprise setups.
We started using Compose for Desktop before it was even publicly announced, so we were often the first to encounter any issues that arose with the framework. As pioneers of Compose for Desktop, we noted all sorts of problems when we started, and reported all of them to our colleagues in the Compose Multiplatform team. They were very helpful and responsive and fixed all of them very quickly. At times, we were able to get a new release with a fix on the same day – very impressive! They also greatly helped us with the adoption of Compose and in cases where we were struggling with the framework.
We were able to make a full clone of our previous design. At first glance, Compose offers fewer layout primitives compared to what we had in HTML/CSS, but it quickly became apparent that simple horizontal and vertical stacks (Row and Column in Compose) already covered 99% of all our needs. When we first started, Compose for Desktop was still missing some pieces, like support for SVG graphics, but our colleagues from the Compose team helped us cover these needs very quickly.
Initially, we used Compose’s Material components throughout the application. These are very well-thought-out components, but they focus on touch interfaces. This means that all elements have large paddings (so they can be easily pressed with a finger), don’t have hover states (given there is no such thing on touch displays), and have very prominent visual touch feedback. On desktop, the story is usually quite different, as components react when hovered over, and the visual feedback for clicks only affects the current element (because it is not covered by a finger). Because of that, we are replacing Material components with our own which work better on desktop.
Did you consider any other UI frameworks before choosing Compose for Desktop?
One alternative would have been to convert the application to a fully native interface, but it would have required three times more effort per feature. We wanted something that would be cross-platform, look nice, and work well with Kotlin.
We felt that Swing was too old and the usage of JavaFX isn’t sufficiently widespread. That’s how we landed on Compose for Desktop, despite it just being announced at the time. Getting direct support from the team and a tight feedback loop also turned out to be a huge plus.
What are the biggest benefits that Kotlin has brought to your product?
Everyday work is just much simpler now. We use the same language across the entire application, meaning the developers on our team share code and knowledge better than before. We’re also having much more fun writing Kotlin instead of C++ and JavaScript!
Do you have any advice or recommendations for our readers?
If you are converting an existing application to a new framework, don’t underestimate the complexity of migration. It is almost like writing a new application from scratch and then some! You’ll inevitably need to re-implement not only the features, but also all of the little nuances in your app’s behavior, whether those are intentional or not.
I strongly believe that Compose for Desktop is the way to create cross-platform desktop applications in 2021. Compared to similar technologies, Kotlin provides access to a tried and tested ecosystem on the JVM, has much greater adoption than Dart and Flutter, and it is far more efficient than Electron with React/JS.
Build your first desktop app with Compose for Desktop
Victor Kropp is the Team Lead for the JetBrains Toolbox App.
Subscribe to Kotlin Blog updates
Discover more
JetBrains Toolbox 案例研究:将 100 万用户迁移到 Kotlin 和 Compose Multiplatform
JetBrains Toolbox 团队的团队负责人 Victor Kropp 分享了在桌面上采用 Kotlin 和 Compose Multiplatform 的故事。
JetBrains Toolbox App 是使用 JetBrains IDE 进行开发的单一入口点。 它是工具和项目的控制面板,可供快速轻松地安装和更新 JetBrains IDE。 该应用程序最初于 2015 年作为编程马拉松项目推出,现在每月为 100 万活跃用户提供服务,并帮助用户在 JetBrains 产品上提高工作效率。
本文介绍了 Toolbox 团队如何将应用程序从 C++ 和 JavaScript 迁移到 100% Kotlin 和 Compose Multiplatform,使代码更易于维护和使用,并交付具有更好运行时性能的更小工件。
Victor,能介绍一下 JetBrains Toolbox 使用的架构和技术堆栈吗?
Toolbox App 是典型的客户端-服务器应用程序。 桌面应用从服务器请求可用工具列表,显示给用户,并根据需要下载 JetBrains 产品的更新。 我们从一开始就使用 Kotlin 实现了应用程序的服务器端部分。 然而,桌面应用程序是另一回事。
在 2015 年开始为 JetBrains Toolbox 构建桌面应用时,我们使用 C++ 实现其业务逻辑,并使用 Chromium Embedded Framework 以及 React 和 HTML/CSS/JS 构建用户界面。 我们在 Kotlin 1.0 尚未发布时做出了这个选择,当时 Java 9 附带的模块化 JDK 也没有发布。 我们负担不起为我们的小型帮助应用程序捆绑数百兆字节的 Java 运行时环境 (JRE),我们也不想让用户必须手动设置环境。 所以,我们选择了一种完全不同的方法。
2021 年,我们将用户界面从 React 迁移到 Compose Multiplatform,更具体地说是 Compose for Desktop,完成了使桌面应用程序 100% Kotlin 化的最后一步。
如何在产品中使用 Kotlin 及其库?
完成向 Kotlin 的迁移后,它就被用于所有地方。 除了 Compose for Desktop 为用户界面提供支持之外,我们还大量使用 kotlinx.coroutines 处理所有异步作业。 Toolbox App 操作了许多 JSON 对象,所以我们会使用 kotlinx.serialization 进行(反)序列化。
服务器端尽可能简单。 事实上,它并不是通常的 HTTP 服务器。 可安装工具的所有信息和描述(在 Toolbox 中为“提要”)均为静态生成,并作为 JSON 文件从 CDN 提供。 它们不经常变动,因此我们仅在有工具的新版本被发布到 TeamCity 持续交付管道时才对其更新。 生成器是一个简单的命令行 Kotlin 程序,作为“构建配置”(TeamCity 的作业名称)调用,在每个受支持产品的每个构建上自动触发。 第二个作业会定期合并所有新生成的提要,舍弃过时的提要,并执行验证。
为什么 Toolbox 团队决定为桌面应用程序开发使用 Kotlin?
在转向 Compose for Desktop 之前,我们使用 Chromium Embedded Framework 为 Toolbox App 构建用户界面,并通过原生 C++ 的业务逻辑轻松支持所有主要桌面操作系统。 (此后发生了很多变化,我们决定在 2020 年将所有 C++ 业务逻辑迁移到在 JVM 上运行的 Kotlin。)
在 2015 年,这些都是启动项目的绝佳选择! 我们能够重用 Ring UI 中的组件,Ring UI 是一个由 JetBrains 构建的 Web UI 组件库。 我们之前在 Web 开发和使用 React 方面也有很多经验。
但是,它也有缺点:
- Chromium Embedded Framework 以其资源消耗闻名。 即使在空闲时,JetBrains Toolbox 也会使用至少 200 MiB 的 RAM。 即使窗口不可见,我们也无法卸载整个框架,因为这会导致用户在尝试与应用交互时出现数秒的延迟。
- 我们需要在单个桌面应用程序中建立成熟的客户端-服务器架构。 嵌入式 Web UI 和业务逻辑是由不同人员用不同语言编写的。 这使开发流程变得较为复杂,并且需要资源在应用程序内部来回发送数兆字节的 JSON,在已经存在的数据(反)序列化上耗尽 CPU 资源。
在将应用程序迁移到 100% Kotlin 之后,这种情况得到了显著改善:
- Compose for Desktop 大幅降低了资源密集程度。 与我们的 JavaScript 实现相比,Compose 框架提供了更好的运行时性能,并且,在后台空闲运行时,我们设法大幅减少了应用使用的 RAM。
- 使用一种语言意味着每个开发者都可以专注于开发功能,无需切换上下文。 它更快,更不易出错,并改善了开发者之间的知识分享。 整个应用程序也对内存中的数据使用相同的表示,跳过了额外(反)序列化步骤。
您能分享一下将 Kotlin 引入 Toolbox 的经历吗?
我们面临着许多挑战。 首先,我们需要将一个已有五年历史的代码库连同其所有功能和特性迁移到不同的堆栈。 为了确保应用程序的核心按预期工作,我们迁移了所有单元测试。 但是,我们的应用程序需要很多外部依赖项,这在不同的生态系统中显然也有所不同。 有些东西在以前的实现中不起作用,而仅仅是因为有新的依赖项支持,就能直接在新的实现中运作。 同时,其他被我们视为理所当然的东西则不再有效。 在某些情况下,我们直到公开发布后才知道这些差异。 这两个类别对应的是操作系统集成的不同方面,例如系统托盘(菜单栏)图标,或代理服务器和 SSL 证书。 另一方面,它还允许我们重用 JetBrains 其他团队编写的 Kotlin 代码,例如在 Toolbox Projects(项目)选项卡中重用 IntelliJ IDEA 的项目搜索的驱动代码或特定企业设置的检测代码。
我们甚至在公开宣布之前就开始使用 Compose for Desktop,因此我们通常是第一个遇到任何框架问题的人。 作为 Compose for Desktop 的先驱,我们在一开始就注意到了各种各样的问题,并将所有问题都报告给了 Compose Multiplatform 团队的同事。 同事们非常热情,反应迅速,很快修正了所有问题。 有时,我们当天就能获得带有修正的新版本,这真的相当了不起! 在我们采用 Compose 的过程中,以及在框架上出现困惑时,这些同事都提供了很大的帮助。
我们能够完全克隆之前的设计。 第一眼看下来,相较于 HTML/CSS,Compose 提供的布局原语似乎更少,但实际上简单的水平和垂直堆栈(Compose 中的行和列)已经涵盖了我们所有需求的 99%。 最开始,Compose for Desktop 仍然缺少一些部分,比如对 SVG 图形的支持,但是 Compose 团队的同事很快就为我们满足了这些需求。
起初,我们是在整个应用程序中使用 Compose 的 Material 组件。 这些组件都经过了精心设计,但专注于触摸界面。 这意味着所有元素都有大内边距(易于手指按下),没有悬停状态(触摸屏上没有这种东西),并且具有非常突出的视觉触摸反馈。 在桌面上就完全是另一回事了,因为组件在悬停时会做出反应,并且点击的视觉反馈仅影响当前元素(因为没有手指覆盖)。 因此,我们正在用我们自己更适合桌面的组件替换 Material 组件。 我们还计划在未来开源我们的组件库,敬请期待。
在选择 Compose for Desktop 之前,你们是否考虑过其他 UI 框架?
有一个备选方案是将应用程序转换为完全原生界面,但这让每个功能都需要三倍的工作量。 我们想要的是跨平台、美观且与 Kotlin 配合良好。
我们觉得 Swing 太老了,JavaFX 的使用还不够广泛。 所以我们选择了 Compose for Desktop,尽管当时它刚刚公布。 团队的直接支持和紧密的反馈循环也是一项巨大优势。
Kotlin 给产品带来的最大好处是什么?
日常工作现在简单多了。 我们在整个应用程序中使用相同的语言,这意味着团队中的开发者能够更好地共享代码和知识。 从 C++ 和 JavaScript 迁移到 Kotlin 后,编写起来也更有趣!
您对我们的读者有什么建议吗?
如果要将现有应用程序转换到新框架,那么不要低估迁移的复杂程度。 这就像是从头开始编写新的应用程序! 不仅功能需要重新实现,应用行为中的所有细微差别,无论有意无意,也都要重新实现。
我坚信 Compose for Desktop 是 2021 年创建跨平台桌面应用程序的绝佳方式。 与同类技术相比,Kotlin 提供了 JVM 上稳定可靠的生态系统,采用率远高于 Dart 和 Flutter,效率远高于带有 React/JS 的 Electron。
使用 Compose for Desktop 构建您的第一个桌面应用
Victor Kropp 是 JetBrains Toolbox App 的团队负责人。
本文英文原作者:
