Documentation
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.
Start with the shape of the answer rather than the list. HotSwan 2 runs your new code in an interpreter inside the running app, so the question is no longer which edits the runtime will accept. It is how much new code you want to write.
Structural changes, up to the whole screen
Everything further down this page is a category. This one is the ceiling, and it is worth seeing before the categories, because it sets what the rest of them are measured against.
HotSwan 2 carries its own interpreter engine into the running app. New code is executed rather than installed, which means the reload is not negotiating with the runtime over which edits it will tolerate. You can add and remove composables, change how a screen branches, wrap or unwrap layout, split one composable into three, and keep going until the file bears no resemblance to what it was.
In the clip below a detail screen is not being tuned. It gets a new background field, a new information hierarchy, and a color language that shares nothing with what was there. The app never restarts.
State survives it. The process is the same one it was before the edit, the navigation stack still knows where you came from, your scroll position is where you left it, and every remember{} value the change did not touch is still holding what it held.
So read the rest of this page as detail rather than as a boundary. The categories below describe how particular edits behave and what is worth knowing about each, not a fence around what the engine can carry. The genuine edges are collected in Limitations, and there are only a few of them.
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:
@Composable
fun Greeting() {
Text(
text = "Hello",
color = Color.Gray,
fontSize = 16.sp
)
}@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.
@Composable
fun ContentPanel() {
Box(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
.height(200.dp)
) {
// content
}
}@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.
@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)
)
}@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.
@Composable
fun StatusCard(isLoading: Boolean) {
if (isLoading) {
CircularProgressIndicator()
}
}@Composable
fun StatusCard(isLoading: Boolean) {
if (isLoading) {
CircularProgressIndicator()
} else {
Icon(
imageVector = Icons.Default.Check,
tint = Color.Green
)
Text("Ready")
}
}Primitive literal tweaks
When your edit only changes a literal value, a color hex, a dp number, a font size, a string, an animation duration, HotSwan skips the entire incremental compilation step and patches the value directly on the running device. The result is visible in under 50 milliseconds from the moment you save the file.
This makes it practical to fine tune colors, spacing, and typography by eye, iterating as fast as you can type. There is no build step, no class swap, no recomposition delay.
@Composable
fun ProfileCard() {
val accent = Color(0xFF6C5CE7)
Box(
modifier = Modifier
.background(accent)
.padding(16.dp)
.size(100.dp)
)
}@Composable
fun ProfileCard() {
val accent = Color(0xFFFF5722)
Box(
modifier = Modifier
.background(accent)
.padding(24.dp)
.size(150.dp)
)
}Color hex values, integer/float/long/double literals, string literals, and string template fragments all qualify. Resource value edits in strings.xml, colors.xml, and dimens.xml are routed through the same fast path. See Literal Patching for the full set of qualifying edits and how the underlying patch pipeline works.
Adding a new composable
You can define a new composable function and call it from an existing function, even across different files. 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:
@Composable
fun Profile() {
Avatar(user)
UserName(user)
}@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:
- No inline functions: Inline functions are expanded at call sites during compilation, so there is no discrete unit to redefine. Extension functions and vararg functions are fully supported, and so is coroutine code written as a LaunchedEffect or a launch block.
- 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 in the ordinary case moving them around does not cause state to be lost or mismatched. All remember{} values stay associated with the correct composable after reordering.
@Composable
fun Profile() {
Avatar(user)
UserName(user)
Bio(user)
}@Composable
fun Profile() {
UserName(user)
Bio(user)
Avatar(user)
}You can also add or remove composable calls as part of the reordering. The compiler plugin tracks each composable by identity, so additions and removals are handled alongside positional changes. Reordering is the one category where it is worth knowing the edge. Compose tracks a composable's state by a key derived from the call, and for several calls to the same composable in one body that key follows source order. Swapping two such calls therefore swaps their keys, so their state follows the position rather than staying with what you think of as the same element. Deeply nested or lazy layouts can land in the same place. If a reorder leaves a screen holding state that looks like it belongs somewhere else, that is the slot table being reconciled against a shape it cannot match, and a rebuild settles it. See Limitations for details.
Relaunching effects
When you change the key or body of a LaunchedEffect, the effect is cancelled and relaunched with your new code on the next recomposition. This means you can iterate on side effects, such as showing a toast or fetching data, without restarting the app.
Toast with a changed value
Change the string value inside remember and the LaunchedEffect fires again with the new key, showing an updated toast on device instantly:
val context = LocalContext.current
val name by remember {
mutableStateOf("hello world")
}
Text(text = name)
LaunchedEffect(name) {
Toast.makeText(context, name, Toast.LENGTH_SHORT).show()
}val context = LocalContext.current
val name by remember {
mutableStateOf("hot reload works!")
}
Text(text = name)
LaunchedEffect(name) {
Toast.makeText(context, name, Toast.LENGTH_SHORT).show()
}ViewModel fetch with a changed parameter
Change the page value and the LaunchedEffect relaunches, calling the ViewModel to fetch new data and updating the list on screen:
val pokemonList = remember {
mutableStateListOf<Pokemon>()
}
val page by remember { mutableIntStateOf(2) }
LaunchedEffect(page) {
homeViewModel.fetchNextPokemonList(page)
.collect {
pokemonList.clear()
pokemonList.addAll(it)
}
}val pokemonList = remember {
mutableStateListOf<Pokemon>()
}
val page by remember { mutableIntStateOf(5) }
LaunchedEffect(page) {
homeViewModel.fetchNextPokemonList(page)
.collect {
pokemonList.clear()
pokemonList.addAll(it)
}
}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:
<color name="primary">#3B82F6</color>
<color name="background">#FFFFFF</color><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:
fun formatPrice(amount: Double): String {
return "$${amount.toInt()}"
}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, including on a class that is already part of the installed app.
Here is an example. You add a nickname property to a data class and display it through toString() in a composable:
data class User(
val name: String,
val email: String,
)
@Composable
fun UserCard(user: User) {
Text(text = user.toString())
}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:
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)
}
}
}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, 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, compiling them into independent compilation units just like regular functions. Coroutine code is covered too, with one distinction worth knowing, below.
Extension functions
You can modify or add extension functions and see changes instantly. The receiver is handled transparently by the compiler plugin.
fun String.toDisplayName(): String {
return this.trim()
}fun String.toDisplayName(): String {
return this.trim().replaceFirstChar { it.uppercase() }
}Suspend lambdas
The coroutine code you edit inside a composable hot reloads. That covers the shapes you write most: LaunchedEffect { … }, produceState { … }, and a launch { … } from a remembered scope. HotSwan rewrites the state machine the Kotlin compiler generates for them, so the continuation machinery is handled for you.
A named suspend fun declared at the top level is the exception and stays native for now. Kotlin lowers it after HotSwan's pass runs, so its body is not one of the units the reload carries. Edit the calling composable, or the lambda inside it, and the change reaches the screen as usual.
LaunchedEffect(id) {
user = api.getUser(id)
}LaunchedEffect(id) {
val fetched = api.getUser(id)
user = fetched.copy(name = fetched.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.
fun buildLabel(vararg parts: String): String {
return parts.joinToString(" ")
}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.
There is still a distinction between a library module and the app module, but it is not about packaging. Only an application module carries the reload task, so an edit in a library is routed to the app module's task and applied from there. HotSwan resolves that itself, and it is why applying the Gradle plugin to the app module is enough for a whole multi module project.