Say Hello to 'Compose Hot Reload' (Firework)

Table of contents
- The current state of Compose Hot Reload
- Demo 1: Simple Literal Change
- Demo 2: Simple Layout Change
- Demo 3: Add State and change the interaction with it
- Demo 4: Adding a new function
- Demo 5: Rename a function (ABI changes)
- Demo 6: Adding a new Class
- Demo 7: Adding a new property to an existing class
- Demo 8: Compose Hot Reload understands code dependencies!
- Demo 9: Reload Status Indicator
- Demo 10: 'Dev Tooling': Clean Composition
- Implementation Details / Technical part
- Future Plans

Disclaimer: This is a public version of an internal blog post within JetBrains. Some parts were removed to save some surprises for later.
Video Version: https://youtu.be/I_FMnPaEBEA
Gullible me; I posted a screen recording into the #kotlin-random Slack channel on October 8th. Alongside this screen recording, I claimed:
"I think it would be easily doable to build a stateful hot-reload if*[…]*".
I wanted to create educational content about ClassLoaders and thought it would be great to showcase building a hot reloadable UI for Compose.
The if part in my initial message was much bigger than expected (who would have thought). But the demos sparked a lot of interest, and with the motivation and many good technical suggestions from our colleagues, two months later, a promising prototype is available.
This blogpost will explain the current state of said prototype (with a few demos), tries to give some technical insights on how it works and finally shares the plans on moving forward.
The current state of Compose Hot Reload
Prototype: https://github.com/JetBrains/compose-hot-reload
The state of the current prototype can be quickly summarized as:
Almost all changes to your code are allowed, except removing supertypes from a class (see DCEVM part later)
Changes in bytecode associated with a given "scope" in Compose will lead to this scope being invalidated (remembered values will be re-calculated)
Gradle is fully implemented, Amper is 'work in progress', Support for the IntelliJ build will be next
Demo 1: Simple Literal Change
This is the 'Hello World' of Hot Reload; Notice the reloading indicator getting yellow once the compilation starts and turning green after the reload is finished.
Demo 2: Simple Layout Change
As it was promised earlier that almost arbitrary code changes are possible, I expect this demo to be almost boring. The UI switches the Layout from a Row to a Column and back.
Demo 3: Add State and change the interaction with it
Now it seems to get more interesting. This demo will introduce a 'remembered' state (count), adds a Button which will increase this count and add a Text
to render it. You can see that pressing the Button will increase the counter on screen. Changing count++
to count++
will change the behaviour of the Button and the counter gets decreased afterwards.
Demo 4: Adding a new function
This Demo shows the power of our JetBrains Runtime. Adding this new 'NewComposable' function is not a problem. Traditionally, such changes are not permitted in the JVM world.
Demo 5: Rename a function (ABI changes)
This demo also shows a typical limitation of hot reloading being lifted (thanks to the JetBrains Runtime). Renaming the 'NewComposable
' function to 'NewName
' is possible. Adding the :)
afterwards reveals that the hot reload is still alive.
Demo 6: Adding a new Class
A new class 'NewClass
' is added and rendered as a String. You can see the default .toString()
method rendering the instance after reloading.
Demo 7: Adding a new property to an existing class
One of the first questions people do have with such a potent hot reload implementation is "What will happen if we add a field/property to an existing class?" . The demo above shows the App
Composable being invalidated, a new instance being created and rendered. This is the easier case. The JetBrains runtime also supports keeping previous instances when adding fields. In this case the JVM will fill new fields with the JVM defaults (null, 0, false).
Demo 8: Compose Hot Reload understands code dependencies!
This one makes me very proud; however, it is a little bit tricky to see what is going on here.
Compose Hot Reload understands which code (and its associated Compose Groups) depend on other functions and 'scopes' (more technical explanations can be found in later sections).
In this demo, we can see the App
Composable picking a random value on the first run and remembering it, even retaining it in later changes. This random value is produced by the calcualteMagic
function. Now look close! The App
Composable gets invalidated, and a new value will be picked by us changing the body of the calculateMagic
function. The runtime will track which code is changed and will resolve all Composable groups which are supposed to be invalidated.
Demo 9: Reload Status Indicator
We all have been there once in our professional life: Debugging for hours on end, starting to doubt our entire model of the universe, just to find out that our code was not even running at all. This is a very unpleasant experience and one of the reasons why developers might lose trust in ‘hot reload technologies’. In this demo, you can see a status indicator glowing red, next to your application, making it obvious that your code was not reloaded.
Once the compilation error was fixed, the indicator jumps from red to yellow back to green.
Demo 10: 'Dev Tooling': Clean Composition
The floating 'Compose Icon', following your application, can also be pressed to open advanced tooling. This is tooling is rendered in a separate process, which can be helpful to recover your application from a very broken state. In this demo, the 'Clean Composition' button is pressed, which will clear all remembered states of the UI and cancels all effects.
Implementation Details / Technical part
Overview
The prototype contains several conceptional parts:
Build Tool Integration
While there is an Amper integration currently 'work in progress', lets focus on the Gradle implementation for the sake of simplicity.
The build tools job is to launch a given Compose application in a special 'hot' mode. This requires
Ensure a JBR is available (download if necessary)
Set necessary JVM arguments to allow redefining classes
Attach and setup the 'Compose Hot Reload Agent'
Provide and 'engage' the 'Compose Hot Reload Runtime'
Provide instructions to the application on how to file a 'recompile request' back to the build tool.
Orchestration Server
We will see later that multiple processes are involved in 'orchestrating' the hot reload application (Dev Tooling Process, Recompiler Process, optionally IDE tooling, …). Those processes communicate through a simple protocol which allows sending messages (OrchestrationMessage) as broadcasts to all participants. If not hosted outside (e.g. by the IDE), the 'Hot Reload Agent' will start this Orchestration Server and tell child processes how to connect to it.
Compose Hot Reload Agent
The agent is responsible for setting up the Hot Reload environment. It will try to find the Compose Runtime and enable a 'Hot Reload Mode', which was initially intended to be used for Android (Live Edit). This mode will allow runtime errors to potentially be recoverable.
If not hosted somewhere outside (e.g. by an IDE plugin), the agent will start the 'Orchestration Server' and launch the 'Dev Tooling Process' as well as the 'Recompiler Process'.
A hook into the JVM will be installed. This hook will allow analyzing the actual bytecode when initially loading a class and when redefining it. This 'runtime analysis' will then know which 'Compose Groups' to invalidate by looking at changes to the bytecode.
The Agent will handle 'ReloadClassesRequest' messages, sent from the 'Recompiler Process', using the JBRs implementation of DCEVM (Enhanced class redefinition for Java)
Runtime Bytecode Analysis
Let's look into how the Compose Hot Reload Agent knows which states / Compose Groups to invalidate. We'll pick a simple example, where @Composable fun Foo
has a simple remembered state, a Button
to interact with said state and two utility functions.
When initially loading the class containing the Foo
function, the Agent will track the 'Runtime Information'. After building 'Compose Aware' bytecode tokens, all functions will be parsed into a 'Runtime Tree'
This 'Runtime Tree' will understand the bytecode in functions and builds the corresponding scopes (called 'Compose Group'). You can see that the simple example will start a RestartGroup
in the Function Foo
. The tree will also include branches in your code.
Once all scopes/groups are parsed, the Runtime Info will be constructed, assigning each scope a 32-bit hash value. This hash value is almost the hash of the bytecode inside the given scope. For obvious reasons some parts are ignored (such as line number depending constants/instructions). If this hash changes, we can assume that the body of the function changed significantly enough to invalidate the group.
This 'Runtime Info' also tracks dependencies between functions!
See how Foo
depends on FooKt.paddingValue
and FooKt$Foo$2
(lambda inside the Button {}
call) depends on FooKt.buttonText
When classes are 'reloaded'/'redefined', the Compose Hot Reload agent will calculate which Groups to invalidate. Another 'key' will be resolved, the 'Invalidation Hash Key'. This hash is calculated by creating a compound hash of a scope with all its dependencies included (transitively). If such an 'Invalidation Hash Key' changes after reloading classes, the associated Compose Groups will be invalidated.
Note
This obviously requires the actual Compose Group Keys to be stable during a reload. The Compose compiler required three patches to guarantee stable enough keys. Those patches will also improve the reliability of other tools (such as Google's 'Live Edit'). Two out of those three patches landed in kotlin.git/master (compose compiler). The third patch is currently pending. Therefore, custom 'firework' builds of the Kotlin/Compose Compiler are required when using the prototype.
Engineering Goode: Look Ma, Screenshot Tests with Hot Reload
Testing a system across multiple processes is tricky. Gaining confidence of Hot Reload really requires some kind of 'visual verification'. Screenshot tests, for example, will host the Orchestration Server in the test process, compile provided source code, spin up a special 'Application Under Test' and communicate with the Application Process by sending 'Orchestration Messages'.
The 'initial source code' here will define an application with a simple counter-state, which will render a text Before: 0
initially. You can see that the 'application under test' allows reacting to 'TestEvents'. Receiving a 'TestEvent' will increase the counter.
We'll take an initial screenshot, send two events, verifying the counter works by taking screenshots 'before-1' and 'before-2'.
Finally, the text "Before" will be replaced with "After" in code and the application reloads hot. The final screenshot will verify the state being retained (by rendering After: 2
)
A test failure would then show up as:
Future Plans
The time since October was mainly used to validate the concept, trying to find as many issues as possible, trying to find any good reason not to continue. There was plenty of sweat and blood put into this project (although, no tears, because I am a brave engineer, not even a little, I promise 👀). My goal was to finish this experimental stage before new year.
I am happy to announce that the project is moving into its next phase: Stabilization. 🎇
Stabilization
Stabilizing the JBR implementation of Hot Reload requires checking the following boxes
(Next to the obvious part of finding bugs and issues and fixing them)
All patches into the Compose Compiler need to be available in a stable Kotlin Release
- Target: Kotlin 2.1.20 | Potentially Kotlin 2.2
Ide Plugin is available
Currently prototyped by @Konstantin Tskhovrebov
Allows starting 'DevelopmentEntryPoint' annotated
@Composable
functionsHosting the OrchestrationServer + ensuring that reloads are explicit by the user (CMD + S)
Provides the JBR, no need to download another one.
Support for IntelliJ Plugin development (Optional, but very desirable)
- The IntelliJ Plugin Gradle plugin integrates Compose Hot Reload. Allowing for a very smooth plugin development experience.
Support for IntelliJ development in
ultimate.git
(Optional, but desirable, help needed!)- When working on IntelliJ inside our monorepo with Compose, allowing run configurations to start in 'Compose Hot Reload Mode' would be great.
Dreaming Bigger 1: Kotlin Preview JVM Compiler
The first stabilization focuses on the JVM and the JetBrains Runtime in particular. However, there might be many projects which might only target Android or (Android + iOS). Adding a JVM target to the project is doable but requires restructuring code and is not particularly cheap (especially for larger projects). One of the bigger pain points might be providing all 'actual' implementations on the JVM which have been defined in common code.
A special 'JVM Preview Compiler' is imagined, which is able to take your common code, producing executable JVM bytecode. Missing 'actuals' can be mocked! If your hot reload session encounters an actual implementation, which has not been defined for the JVM, then certain levels of gracefulness can be used.
Missing actuals
returning Unit
or nullable types could return null
or Unit
.
A placeholder box@Composable
could replace missing functions @Composable
functions.
Exceptions can be thrown otherwise.
All incidents, when your 'preview hot reload session' runs into a missing actual will be reported by the runtime as 'Orchestration Message', which could be picked up by the IDE suggesting to create the missing actual for you.
Note: This is a simple description of what we might want to work on next. I am sure that many readers will be able to point out many issues and flaws with the exact workings described above. Thats OK.
Dreaming Bigger 2: Next Targets | Android, iOS, Wasm
// To be continued…
Subscribe to my newsletter
Read articles from Sebastian Sellmair directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sebastian Sellmair
Sebastian Sellmair
Working on Kotlin at JetBrains.