Limitations

Hot reload on a real Android device is NOT easy. The Android Runtime (ART) imposes strict constraints on class redefinition. It does not allow changes to class structure, memory layout, or method signatures at runtime. Working within these boundaries requires carefully bypassing low-level restrictions while keeping the running app stable.

The runtime's class swap mechanism can replace method bodies, but it cannot change the shape of a class. This means you cannot add or remove fields, change method signatures, or alter the class hierarchy through hot reload. When HotSwan detects a change that would violate these constraints, it automatically falls back to a full incremental build.

This page explains each limitation, why it exists, and what happens when HotSwan encounters it.

Why limitations exist

The Android runtime enforces a strict rule when swapping classes at runtime: the new class definition must have exactly the same schema as the old one. Same fields, same method signatures, same interfaces. Only method bodies can change.

HotSwan's compiler plugin (independent compilation, parameter reservation) works within these constraints to maximize what you can hot reload. But some changes inevitably fall outside what the runtime allows.

Adding or removing composable/Kotlin functions

Adding a new function within the same file can be hot reloaded under certain constraints. Cross-file additions and removals require a full rebuild.

The compiler plugin compiles each function body into its own discrete compilation unit. When you add a new function, a new unit is emitted and loaded into the running process. Callers within the same source file are resolved at the bytecode level without modifying any existing class schema.

This means adding a new composable function and calling it from the same file works without a rebuild. However, the following constraints apply:

  • Same file only: Cross-file references introduce additional linking constraints at the bytecode level that cannot be resolved through in-memory class redefinition.
  • No inline functions: Inline functions are expanded at call sites during compilation, so there is no discrete unit to redefine. Suspend functions, extension functions, and vararg functions are fully supported.
  • Removing functions: Removing a function does not change any existing class schema (the corresponding compilation unit simply becomes unreachable), so it can be hot reloaded. However, if the removal triggers lambda renumbering, a full rebuild is required.

When any constraint is violated, HotSwan detects the structural change and automatically falls back to a full incremental build.

Data class property changes

Adding new properties to a data class can be hot reloaded. Removing properties or changing their types still requires a full rebuild.

Data class properties compile to JVM fields plus their corresponding getter and setter methods. The Kotlin compiler also generates copy(), equals(), hashCode(), and toString() methods that reference all fields.

When you add a new property, HotSwan performs a binary-level schema transformation that reconciles the existing class layout with the updated definition. The runtime sees the change as compatible and applies it without a restart.

Removing a property or changing an existing property's type fundamentally alters the memory layout in a way that cannot be reconciled at runtime, so these changes trigger a full rebuild.

Constructor changes

Adding constructor parameters to a data class can be hot reloaded. Other constructor changes require a full rebuild.

Constructor parameters map to JVM fields and the <init> method signature. Adding a constructor parameter adds a field and changes the constructor's method descriptor. The runtime treats this as a schema change.

For data classes, HotSwan applies the same binary-level schema transformation used for property addition, so the runtime accepts the new constructor without a rebuild. For other classes (ViewModel constructors, repository constructors, etc.), changing the primary constructor still requires a full rebuild.

Interface and superclass changes

Changing implemented interfaces or the superclass triggers a runtime error.

The class hierarchy is part of the class schema. If you add an interface implementation, the runtime sees a different class definition and rejects it with a class hierarchy change error. The same applies to changing the superclass.

Adding new resources

Adding entirely new resources changes the R class field assignments.

Android's resource system assigns integer IDs to every resource at compile time through the R class. When you add a new string, color, or drawable, the resource compiler assigns it a new ID and may shift existing IDs. This changes the R class, which is a class schema change that the runtime cannot handle.

Modifying the value of an existing resource works fine because the R class IDs remain the same. Only the resource table entry changes, which is handled through AssetManager patching.

Lambda count changes

Adding or removing lambdas changes the internal mapping of compiler-generated helper classes. Most cases are resolved automatically, but some still require a full rebuild.

The Android build toolchain compiles lambda expressions into compiler-generated helper classes with ordered identifiers. If you add a new lambda in the middle of a file, the compiler may reassign identifiers for all subsequent helper classes.

HotSwan performs differential analysis on the before and after class mappings to detect and resolve these shifts transparently. In most editing scenarios the resolution succeeds and the reload proceeds normally. However, when the edit introduces a genuinely new helper class that has no corresponding predecessor, the divergence cannot be resolved at runtime and a full build is triggered instead.

Automatic fallback

You do not need to think about these limitations during development. HotSwan performs a structural pre-check before sending any classes to the device. It captures class structure data (methods and fields) from the installed APK as a baseline, then compares the changed classes against this baseline.

If a structural change is detected, HotSwan automatically:

  1. Skips the hot reload.
  2. Runs a full incremental build of the app module.
  3. Installs the updated APK on the device.
  4. Updates the baseline for future comparisons.

The entire fallback process is transparent. You see a message in the HotSwan console indicating that a full build was triggered, and the app restarts with your changes applied.