Skip to main content
Compose Hot Reload aims to support any changes to your application’s source code. However, managing global state during hot reload presents unique challenges that can lead to exceptions or unexpected behavior if not handled properly.

The Challenge

When you make changes to your code, Compose Hot Reload:
  1. Reloads the changed classes
  2. Migrates the heap to the new class definitions
  3. Re-initializes static fields (when necessary)
  4. Invalidates and re-renders affected Compose code
This process ensures that all references to old code are cleaned up. However, Compose Hot Reload cannot automatically invalidate global state (except for static fields), which means changes to global state can cause exceptions.

What is Global State?

Global state includes:
  • Singleton objects
  • ViewModels that persist across recompositions
  • Caches stored in companion objects
  • Service instances
  • Databases and repositories
  • Any data that outlives individual Composable instances

Solutions

When changes to global state cause issues, you have two options:

Option 1: Restart the Application

The simplest solution is to restart your application after making changes that affect global state. This ensures a clean slate.

Option 2: Reset State Using Runtime API Hooks

For a better development experience, you can manually reset global state using the Compose Hot Reload Runtime API.

Using the Runtime API

First, add the runtime API dependency:
build.gradle.kts
implementation("org.jetbrains.compose.hot-reload:hot-reload-runtime-api:<version>")

Non-Composable Option: staticHotReloadScope

Use staticHotReloadScope.invokeAfterHotReload() to register cleanup hooks outside of Composable functions:
import org.jetbrains.compose.reload.isHotReloadActive
import org.jetbrains.compose.reload.staticHotReloadScope
import org.jetbrains.compose.reload.DelicateHotReloadApi

@OptIn(DelicateHotReloadApi::class)
fun setupHotReload() {
    if (isHotReloadActive) {
        staticHotReloadScope.invokeAfterHotReload {
            // Reset your global state here
            GlobalCache.clear()
            ServiceRegistry.reset()
        }
    }
}

Composable Option: AfterHotReloadEffect

Use AfterHotReloadEffect within Composable functions for automatic lifecycle management:
import androidx.compose.runtime.Composable
import org.jetbrains.compose.reload.AfterHotReloadEffect
import org.jetbrains.compose.reload.isHotReloadActive
import org.jetbrains.compose.reload.DelicateHotReloadApi

@OptIn(DelicateHotReloadApi::class)
@Composable
fun MyApp() {
    if (isHotReloadActive) {
        AfterHotReloadEffect {
            // Reset state after each hot reload
            AppState.reset()
        }
    }
    
    // Rest of your app
}

ViewModel Support

One of the most common examples of global state is ViewModel classes from Android’s Architecture Components or similar libraries. ViewModels persist their state through UI changes, which means any data they store will be retained after hot reload.

The Problem

When you change a data class that’s referenced by a ViewModel:
// Before
data class UiData(val label: String = "Hello")

class SimpleViewModel : ViewModel() {
    val data = UiData()
}
// After hot reload - changing the data class
data class UiData(val label: String = "Goodbye")

class SimpleViewModel : ViewModel() {
    val data = UiData()
}
The ViewModel instance created before the reload still holds the old UiData instance with label = "Hello". This can cause:
  • UI not reflecting code changes
  • ClassCastException errors
  • Unexpected behavior

Solution: Reset ViewModels After Hot Reload

Composable Approach

Reset ViewModels within your Composable code:
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import org.jetbrains.compose.reload.AfterHotReloadEffect
import org.jetbrains.compose.reload.isHotReloadActive
import org.jetbrains.compose.reload.DelicateHotReloadApi

@OptIn(DelicateHotReloadApi::class)
@Composable
fun App() {
    if (isHotReloadActive) {
        val viewModelStoreOwner = LocalViewModelStoreOwner.current
        AfterHotReloadEffect {
            viewModelStoreOwner?.viewModelStore?.clear()
        }
    }
    
    // Your app content with ViewModels
    MyScreen()
}

Non-Composable Approach

For more control, use staticHotReloadScope:
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import org.jetbrains.compose.reload.staticHotReloadScope
import org.jetbrains.compose.reload.DelicateHotReloadApi

val viewModelStoreOwner = object : ViewModelStoreOwner {
    override val viewModelStore = ViewModelStore()
}

@OptIn(DelicateHotReloadApi::class)
fun scheduleViewModelsReset() {
    staticHotReloadScope.invokeAfterHotReload {
        viewModelStoreOwner.viewModelStore.clear()
    }
}

fun main() = application {
    scheduleViewModelsReset()
    
    CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
        Window(onCloseRequest = ::exitApplication) {
            MyApp()
        }
    }
}

Complete Example

Here’s a full example showing how to handle ViewModels with hot reload:
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import org.jetbrains.compose.reload.AfterHotReloadEffect
import org.jetbrains.compose.reload.DelicateHotReloadApi
import org.jetbrains.compose.reload.isHotReloadActive

data class UiData(val label: String = "Hello")

class SimpleViewModel : ViewModel() {
    val data = UiData()
}

val viewModelStoreOwner = object : ViewModelStoreOwner {
    override val viewModelStore = ViewModelStore()
}

@OptIn(DelicateHotReloadApi::class)
@Composable
fun App() {
    if (isHotReloadActive) {
        AfterHotReloadEffect {
            viewModelStoreOwner.viewModelStore.clear()
        }
    }
    
    val vm = viewModel { SimpleViewModel() }
    Text(vm.data.label)
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
            App()
        }
    }
}

Best Practices

1. Use isHotReloadActive Guards

Always guard hot reload-specific code to prevent it from running in production:
if (isHotReloadActive) {
    AfterHotReloadEffect {
        resetState()
    }
}

2. Prefer AfterHotReloadEffect in Composables

When working within Composable functions, use AfterHotReloadEffect instead of staticHotReloadScope for automatic cleanup:
@Composable
fun MyScreen() {
    // Good - automatic cleanup
    AfterHotReloadEffect {
        resetViewModel()
    }
}

3. Clear Caches and Registries

Reset any global caches or service registries:
@OptIn(DelicateHotReloadApi::class)
fun setupHotReload() {
    if (isHotReloadActive) {
        staticHotReloadScope.invokeAfterHotReload {
            ImageCache.clear()
            DependencyContainer.reset()
            EventBus.clearSubscribers()
        }
    }
}

4. Handle Database Connections

For database-heavy applications, you may need to refresh queries or connections:
AfterHotReloadEffect {
    database.invalidateQueries()
    repositoryCache.clear()
}

5. Reset Navigation State

If using navigation libraries, consider resetting the navigation state:
AfterHotReloadEffect {
    navController.popBackStack(startDestination, inclusive = false)
}

Common Patterns

Singleton Pattern

object AppConfig {
    private var initialized = false
    
    fun reset() {
        initialized = false
        // Reset other state
    }
}

@OptIn(DelicateHotReloadApi::class)
fun main() {
    if (isHotReloadActive) {
        staticHotReloadScope.invokeAfterHotReload {
            AppConfig.reset()
        }
    }
    
    // Start app
}

Service Locator Pattern

object ServiceLocator {
    private val services = mutableMapOf<Class<*>, Any>()
    
    fun reset() {
        services.clear()
    }
}

AfterHotReloadEffect {
    ServiceLocator.reset()
}

State Management Libraries

For state management libraries like Redux or MVI:
AfterHotReloadEffect {
    store.dispatch(ResetStateAction)
    // or
    stateManager.reset()
}

Troubleshooting

My ViewModel state isn’t updating after hot reload

Make sure you’re clearing the ViewModelStore:
AfterHotReloadEffect {
    LocalViewModelStoreOwner.current?.viewModelStore?.clear()
}

I’m getting ClassCastException after hot reload

This usually indicates that old instances are being cast to new class definitions. Add appropriate reset hooks:
AfterHotReloadEffect {
    // Clear any caches holding old instances
    cache.clear()
}

My app crashes when changing data classes

Data classes used in ViewModels or global state need special handling. Always reset the containing state:
AfterHotReloadEffect {
    viewModelStore.clear()
}

Build docs developers (and LLMs) love