Documentation
Limitations
This page used to be long. HotSwan 1.x reloaded by asking the Android runtime to accept a new version of a class that was already loaded, and the runtime accepts very little, so most of the page was a list of the shapes it refused.
2.0 removed that constraint by carrying its own interpreter engine into the running app. New code is executed rather than installed, so adding and removing composables, changing how a screen branches, wrapping or unwrapping layout, and replacing an entire screen all apply in place. What is left is the short list below.
What changed in 2.0
If you are reading this page because something did not reload, start by checking whether it is on the list at all. These are the cases that used to need a rebuild and no longer do:
- Adding or removing composables and Kotlin functions
- Changing control flow, conditionals, and how a screen branches
- Wrapping or unwrapping layout, and reordering composables
- Replacing a whole screen with code that looks nothing like it did
- Adding or removing lambdas, which used to risk an internal class renumbering that could not be remapped
State survives all of them. Your navigation stack, scroll position, and remember{} values are not touched by a change that does not touch them. See Supported Changes for the full picture.
Class hierarchy changes
Changing the superclass of an already instrumented class, or the set of interfaces it implements, is the one structural edit that cannot be applied in place. Instances of the old shape already exist in memory, and swapping the class underneath them would leave their layout inconsistent with the code now reading it.
What HotSwan does instead: it restarts the app process and applies your edit on the way back up. On Android that restart is automatic. On Desktop you get a message asking you to restart the window.
This is worth contrasting with 1.x, where the same edit meant a full build and a reinstall. A process restart is a different order of cost, and your app comes back with the new code already in it.
New resources
Adding an entirely new resource, a new R.string, R.drawable or similar, needs a reinstall. Modifying the value of an existing resource does not.
The reason is outside HotSwan. Resource IDs are assigned by AAPT2 and baked into the resources.arsc that shipped with the installed APK. A new ID does not exist on the device yet, so code referencing it has nothing to resolve against, and no amount of pushing code can create it.
What HotSwan does instead: it names the classes that reference resources the installed APK does not have, and tells you to reinstall. It does not push them and let them fail later.
Declined on the device
Two cases are decided on the device, after the push arrives, because that is the only place the running composition can be inspected. Both are refusals rather than failures, and they carry different remedies.
Compile mode skew. The reload compiled incrementally and the installed build did not, so the two walk the composition's slot table differently. Applying them would desync the cursor and take the composition down, so HotSwan keeps the installed bodies. Rebuild the module non-incrementally by deleting <module>/build/kotlin.
Slot shape change. The edit changes the composition's shape in a way that cannot be reconciled with what is already on screen. No rebuild of the reload fixes this and neither does restarting it. Rebuild and reinstall the app.
Refused at compile time
Some declarations cannot be carried by the reload, and HotSwan decides that while compiling rather than on the device. When you edit one, the tool window shows an amber line saying the declarations are not hot reloadable and this edit needs a rebuild. Hot reloadable code in the same file is unaffected.
The important property here is that these are refused by name. HotSwan tells you which declarations it skipped instead of applying something subtly wrong, which is the failure mode that costs an afternoon.
Two cases do not get a name yet. inline functions are left to the compiler that inlined them, and the skip is recorded without a reason code a user ever sees. If an edit to an inline function does not appear, that is why, and a rebuild picks it up.
The other is the body of a named suspend fun, which stays native because Kotlin lowers it after HotSwan's pass has run. Coroutine code written where you usually write it does reload: an edit inside a LaunchedEffect, produceState or a launch block reaches the screen as normal. It is editing suspend fun loadUser() itself that waits for a rebuild.
Per platform
Android. Hot reload is a development tool and runs on debug builds. Applying the plugin leaves your release configuration alone. One save reaches one Android device: if several are attached, HotSwan names the one that receives the reload and the ones that do not, rather than leaving you to guess. Compose Desktop and every booted iOS simulator do reload together in that same save.
iOS. The reload target is the simulator. Your Compose entry point has to be a top level, zero argument function returning a UIViewController, and an entry that takes Swift supplied arguments is refused by name. The reload delta tier needs an app with an Embed and Sign phase to attach to, so an XCFramework or SwiftPM binary target consumer uses the other two tiers and is told so. A structural edit on iOS costs more time than the same edit on Android, because the change ships as a dynamic image the running app loads.
Desktop. The reload port has to be free. If a previous run of the same app is still alive, or a second HotSwan desktop app holds it, HotSwan reports that hot reload is disabled for that window rather than binding somewhere the IDE will not look.
Full detail is in Requirements.
How HotSwan degrades
You do not need to memorize this page. HotSwan decides what an edit needs and takes the cheapest route that is safe, in this order:
- Patch the literal, with no compilation at all, when the edit is a value
- Compile and apply the changed code to the running app, which is the normal path for everything structural
- Restart the process when the class hierarchy changed, applying the edit on the way back up
- Refuse by name, and say what to do, when none of the above is safe
The rule behind that order is that a wrong reload is worse than a slow one. HotSwan would rather tell you it cannot carry an edit than apply something that leaves the app running code it cannot account for. If a message surprises you, the Troubleshooting page indexes them by symptom.
Appendix: what 1.x could not do
Kept for anyone still on the 1.x line, and for context on why 2.0 is a different product rather than a faster one. 1.x reloaded through the Android runtime's own class redefinition, which accepts a change to the body of a method and very little else. Everything below meant a full build and a reinstall:
- Adding or removing functions, including composables. The method table of a loaded class could not grow or shrink.
- Data class properties and constructor changes, for the same reason applied to fields.
- Interface and superclass changes, which remain a restart in 2.0, but a rebuild in 1.x.
- Lambda count changes. Adding or removing a lambda could make D8 renumber the synthetic classes in a way that could not be remapped.
- New Kotlin files, since a class that is not in the installed APK cannot be redefined into existence.
- Structural changes inside shared element transitions, which could break the transition state.
There was also a device floor attached to the mechanism rather than to the product: additions-only changes needed API 30 or newer because they relied on ART structural redefinition. 2.0 does not use it, so that recommendation is gone. See Version Compatibility if you are deciding which line to be on.