Supported Changes

HotSwan is not limited to composable functions. You can modify any Kotlin function body, update resource values, and reorder composables, all without restarting your app. This page covers each type of supported change with code examples.

Composable function body changes

The most common use case. You can change anything inside a composable function body: modifiers, text, colors, layout structure, control flow, and composable calls. As long as you are modifying the body of an existing function (not adding or removing functions), HotSwan can reload it.

Here is a simple example. You change the text, color, and font size of a composable:

Before
@Composable
fun Greeting() {
    Text(
        text = "Hello",
        color = Color.Gray,
        fontSize = 16.sp
    )
}
After
@Composable
fun Greeting() {
    Text(
        text = "Hello, World!",
        color = Color.Blue,
        fontSize = 24.sp
    )
}

You can also change layout structure, add or remove child composables within the same function, swap a Column for a Row, or wrap content in a new Box. The HotSwan compiler plugin's independent compilation ensures that only the changed function's class is swapped.

Here are more examples of composable body changes you can hot reload:

Tweak modifier parameters

Adjust layout modifiers like size, padding, and animations on the fly. Tweak modifier parameters and see the result instantly on your real device, making it easy to fine-tune proportions, padding, and transition behavior by eye.

Before
@Composable
fun ContentPanel() {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .animateContentSize()
            .height(200.dp)
    ) {
        // content
    }
}
After
@Composable
fun ContentPanel() {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .animateContentSize()
            .height(300.dp)
    ) {
        // content
    }
}

Tweak animation parameters

Animation tuning is one of the most powerful use cases for hot reload. Instead of guessing values, rebuilding, and waiting, you can adjust animation specs, durations, easing curves, and layout sizes and see exactly how they behave on your real device in real time. Every tweak is reflected instantly, so you can iterate on the feel of your animations by eye.

Before
@Composable
fun PulseIcon(visible: Boolean) {
    val alpha by animateFloatAsState(
        targetValue = if (visible) 1f else 0f,
        animationSpec = tween(300)
    )
    Icon(
        imageVector = Icons.Default.Star,
        modifier = Modifier.alpha(alpha)
    )
}
After
@Composable
fun PulseIcon(visible: Boolean) {
    val alpha by animateFloatAsState(
        targetValue = if (visible) 1f else 0f,
        animationSpec = spring(dampingRatio = 0.4f)
    )
    Icon(
        imageVector = Icons.Default.Favorite,
        modifier = Modifier.alpha(alpha)
    )
}

Change conditional composable logic

Adding, removing, or rewriting if/else and when blocks inside a composable function body is fully supported.

Before
@Composable
fun StatusCard(isLoading: Boolean) {
    if (isLoading) {
        CircularProgressIndicator()
    }
}
After
@Composable
fun StatusCard(isLoading: Boolean) {
    if (isLoading) {
        CircularProgressIndicator()
    } else {
        Icon(
            imageVector = Icons.Default.Check,
            tint = Color.Green
        )
        Text("Ready")
    }
}

Adding a new composable

You can define a new composable function and call it from an existing function in the same file. The HotSwan compiler plugin compiles each function into its own discrete compilation unit, so adding a new function does not alter the schema of any existing class. The new unit is loaded at runtime and the caller is recomposed to pick it up.

Here is an example. You add a new Bio composable and call it from Profile:

Before
@Composable
fun Profile() {
    Avatar(user)
    UserName(user)
}
After
@Composable
fun Profile() {
    Avatar(user)
    UserName(user)
    Bio(user)
}

@Composable
private fun Bio(user: User) {
    Text(text = user.bio)
}

There are a few constraints to keep in mind:

  • Same file only: The new function must be defined and called within the same source file. 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.
  • Addition only: You can add new composable functions via hot reload, but removing a previously added function's definition requires a full rebuild.

When any of these constraints are violated, HotSwan detects it and automatically falls back to a full incremental build. See Limitations for the full list.

Composable reordering

You can reorder existing composable calls within a function body. The HotSwan compiler plugin tracks each composable by its identity rather than its position in the source code, so moving them around does not cause state to be lost or mismatched. All remember{} values stay associated with the correct composable after reordering.

Before
@Composable
fun Profile() {
    Avatar(user)
    UserName(user)
    Bio(user)
}
After
@Composable
fun Profile() {
    UserName(user)
    Bio(user)
    Avatar(user)
}

Note that this only applies to simple reordering of the same composable calls. If the reordering also adds or removes composable calls (which changes the number of generated lambda classes), HotSwan detects a structural change and falls back to a full incremental build automatically. See Limitations for details.

Resource value changes

You can change existing resource values in strings.xml, colors.xml, dimens.xml, and drawable files. HotSwan detects the change, runs processDebugResources to produce a compiled resource APK, and patches the AssetManager in the running app.

For example, changing a color value:

Before
<color name="primary">#3B82F6</color>
<color name="background">#FFFFFF</color>
After
<color name="primary">#EF4444</color>
<color name="background">#1A1A2E</color>

The key distinction is between changing existing resource values and adding new resources. Changing a value works because the R class field assignments stay the same. Adding a new resource (a new R.string or R.drawable entry) changes the R class, which requires a full rebuild. HotSwan detects this and falls back automatically.

Non-composable function changes

HotSwan operates at the runtime level, which means it can swap any class, not just composable functions. You can modify ViewModel methods, repository logic, utility functions, data mappers, and any other Kotlin function body.

For example, changing how a price is formatted in a utility function:

Before
fun formatPrice(amount: Double): String {
    return "$${amount.toInt()}"
}
After
fun formatPrice(amount: Double): String {
    return "$${String.format("%.2f", amount)}"
}

The change takes effect immediately. Any composable that calls this function will display the updated result on the next recomposition. This is one of the key differences from Live Edit, which only supports changes to composable functions.

Data class property/field addition

You can add new properties and fields to a data class and see them reflected instantly. HotSwan reconciles the class layout at the binary level so the runtime accepts the updated definition without a restart.

Here is an example. You add a nickname property to a data class and display it through toString() in a composable:

Before
data class User(
    val name: String,
    val email: String,
)

@Composable
fun UserCard(user: User) {
    Text(text = user.toString())
}
After
data class User(
    val name: String,
    val email: String,
    val nickname: String = "",
    val age: Int = 0,
) {
    val displayName = "$name ($nickname)"
}

@Composable
fun UserCard(user: User) {
    Text(text = user.displayName)
    Text(text = user.toString())
}

The composable picks up the new properties and fields immediately. You can add constructor properties, computed fields in the class body, or both at the same time. The toString(), copy(), equals(), and hashCode() methods are all regenerated by the HotSwan Kotlin compiler and swapped at runtime. Note that removing properties or changing their types still requires a full rebuild. See Limitations for details.

ViewModel method changes

You can modify existing ViewModel methods and add entirely new ones. The HotSwan compiler plugin isolates each method into its own compilation unit, so changing business logic or adding a new function does not alter the ViewModel's class schema.

Here is an example. You add a new method to a ViewModel and call it from a composable in the same file:

Before
class HomeViewModel : ViewModel() {
    private val _items = MutableStateFlow(emptyList<Item>())
    val items = _items.asStateFlow()

    fun loadItems() {
        viewModelScope.launch {
            _items.value = repository.getItems()
        }
    }
}

@Composable
fun HomeScreen(viewModel: HomeViewModel) {
    val items by viewModel.items.collectAsState()
    LazyColumn {
        items(items) { item ->
            Text(text = item.title)
        }
    }
}
After
class HomeViewModel : ViewModel() {
    private val _items = MutableStateFlow(emptyList<Item>())
    val items = _items.asStateFlow()

    fun loadItems() {
        viewModelScope.launch {
            _items.value = repository.getItems()
        }
    }

    fun filterByCategory(category: String) {
        viewModelScope.launch {
            _items.value = repository.getItemsByCategory(category)
        }
    }
}

@Composable
fun HomeScreen(viewModel: HomeViewModel) {
    val items by viewModel.items.collectAsState()
    Button(onClick = { viewModel.filterByCategory("popular") }) {
        Text("Popular")
    }
    LazyColumn {
        items(items) { item ->
            Text(text = item.title)
        }
    }
}

Both the new ViewModel method and the updated composable are reloaded together. The ViewModel instance and its state are preserved across the reload, so existing data in StateFlow or LiveData remains intact. This is one of the key differences from a full rebuild, where the ViewModel would be recreated and lose in-memory state.

Extension, suspend & vararg functions

Extension functions, suspend functions, vararg functions, and functions with many parameters are all fully hot reloadable. The HotSwan compiler plugin handles the special calling conventions for each of these function types, compiling them into independent compilation units just like regular functions.

Extension functions

You can modify or add extension functions and see changes instantly. The receiver is handled transparently by the compiler plugin.

Before
fun String.toDisplayName(): String {
    return this.trim()
}
After
fun String.toDisplayName(): String {
    return this.trim().replaceFirstChar { it.uppercase() }
}

Suspend functions

Coroutine-based functions can be hot reloaded just like regular functions. The continuation machinery is handled by the compiler plugin.

Before
suspend fun fetchUser(id: String): User {
    return api.getUser(id)
}
After
suspend fun fetchUser(id: String): User {
    val user = api.getUser(id)
    return user.copy(name = user.name.trim())
}

Vararg functions

Functions with variable-length argument lists are supported. You can modify the body of a vararg function and see the result immediately.

Before
fun buildLabel(vararg parts: String): String {
    return parts.joinToString(" ")
}
After
fun buildLabel(vararg parts: String): String {
    return parts.joinToString(" · ")
}

Multi-module changes

HotSwan works across multi-module projects. When you save a file, the plugin resolves which Gradle module the file belongs to by matching the file path against your project's module structure. It then runs the incremental build on that specific module.

For example, if you edit a composable in :feature:home, HotSwan compiles only that module and applies the changed classes. You do not need to configure anything for this to work. Module detection is automatic.

Feature module DEX files go through a different merge path than app module DEX files. HotSwan handles this distinction internally, selecting the correct Gradle task based on the module type.