Documentation Index
Fetch the complete documentation index at: https://mintlify.com/JetBrains/compose-hot-reload/llms.txt
Use this file to discover all available pages before exploring further.
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:
- Reloads the changed classes
- Migrates the heap to the new class definitions
- Re-initializes static fields (when necessary)
- 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:
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()
}