Skip to main content
The HotReloadScope class and staticHotReloadScope object provide a way to register hooks that execute after hot reload events, outside of composable context.

HotReloadScope

HotReloadScope is an abstract class that provides methods for interacting with hot reload functionality.
@SubclassOptInRequired(InternalHotReloadApi::class)
public abstract class HotReloadScope internal constructor()
This class cannot be subclassed outside of the Compose Hot Reload library. Use the staticHotReloadScope instance to access functionality.

invokeAfterHotReload()

Registers an action to be executed after a hot reload.
public abstract fun invokeAfterHotReload(action: () -> Unit): AutoCloseable
action
() -> Unit
required
A lambda function called after each hot reload on the main thread. This action should not throw exceptions. If an exception occurs, it will be re-thrown at the end of the main thread’s dispatch queue.
return
AutoCloseable
An AutoCloseable handle that can be used to unregister the hook. Call close() on the returned object when you no longer want the action to be invoked after reloads.

Timing Guarantees

The registered action is called:
  1. After changed classes have been reloaded
  2. After the heap has been migrated to new class versions
  3. After changed static fields have been re-initialized
  4. Before the next frame is rendered

Exception Handling

The action lambda should not throw exceptions. However, if an exception does occur:
  • The exception will be caught
  • It will be re-thrown at the end of the main thread’s dispatch queue
  • Other reload hooks will still execute

Example

import org.jetbrains.compose.reload.DelicateHotReloadApi
import org.jetbrains.compose.reload.staticHotReloadScope

@OptIn(DelicateHotReloadApi::class)
class ResourceManager : AutoCloseable {
    private val reloadHook: AutoCloseable
    
    init {
        reloadHook = staticHotReloadScope.invokeAfterHotReload {
            println("Reloading resources...")
            reloadResources()
        }
    }
    
    private fun reloadResources() {
        // Refresh cached resources
    }
    
    override fun close() {
        // Clean up the hook when done
        reloadHook.close()
    }
}

staticHotReloadScope

A static singleton instance of HotReloadScope for registering reload hooks from non-composable code.
@DelicateHotReloadApi
public expect val staticHotReloadScope: HotReloadScope
Requires @OptIn(DelicateHotReloadApi::class)This API requires careful resource management. You must explicitly close the returned AutoCloseable to prevent memory leaks.

When to Use

Use staticHotReloadScope when:
  • You need to register reload hooks from non-composable code
  • You’re managing long-lived objects that need to react to reloads
  • You have control over the lifecycle and can guarantee cleanup

When NOT to Use

Avoid staticHotReloadScope when:
  • You’re in a composable function (use AfterHotReloadEffect instead)
  • You can’t guarantee the hook will be cleaned up
  • The lifecycle is unclear or complex

Complete Example

Here’s a complete example showing proper lifecycle management:
import org.jetbrains.compose.reload.DelicateHotReloadApi
import org.jetbrains.compose.reload.isHotReloadActive
import org.jetbrains.compose.reload.staticHotReloadScope

@OptIn(DelicateHotReloadApi::class)
class DatabaseConnection : AutoCloseable {
    private var reloadHook: AutoCloseable? = null
    
    init {
        // Only register hook if hot reload is active
        if (isHotReloadActive) {
            reloadHook = staticHotReloadScope.invokeAfterHotReload {
                onHotReload()
            }
        }
    }
    
    private fun onHotReload() {
        println("Hot reload detected, refreshing connection pool")
        // Refresh database connection pool
        // Clear caches
        // Re-validate connections
    }
    
    override fun close() {
        // Always clean up the hook
        reloadHook?.close()
        // Clean up database resources
    }
}

fun main() {
    val connection = DatabaseConnection()
    try {
        // Use connection
    } finally {
        connection.close()
    }
}

Memory Management

Critical: Always Close HooksFailure to close the AutoCloseable returned by invokeAfterHotReload() will cause memory leaks. The hook will continue to hold references to your lambda and its captured variables.

Best Practices

  1. Store the AutoCloseable: Always store the returned AutoCloseable in a variable
  2. Implement AutoCloseable: Make your class implement AutoCloseable if it registers hooks
  3. Use try-finally: Wrap usage in try-finally blocks to ensure cleanup
  4. Prefer AfterHotReloadEffect: In composables, use AfterHotReloadEffect which handles cleanup automatically

Anti-Pattern Example

// DON'T DO THIS - Hook is never cleaned up
@OptIn(DelicateHotReloadApi::class)
fun registerGlobalHook() {
    staticHotReloadScope.invokeAfterHotReload {
        println("Reloaded")
    }
    // Hook is never closed - memory leak!
}

Correct Pattern

// DO THIS - Hook is properly managed
@OptIn(DelicateHotReloadApi::class)
class Service : AutoCloseable {
    private val hook = staticHotReloadScope.invokeAfterHotReload {
        println("Reloaded")
    }
    
    override fun close() {
        hook.close()
    }
}

Platform Behavior

On JVM platforms with hot reload active, invokeAfterHotReload() registers a real hook with the hot reload agent. On all other platforms (or when hot reload is not active), the implementation is a no-op:
  • The action lambda is never called
  • The returned AutoCloseable.close() does nothing
  • No performance overhead

Safe Alternative

Prefer AfterHotReloadEffect in ComposablesIf you’re working in a composable function, use AfterHotReloadEffect instead. It automatically manages the lifecycle of the hook and cleans up when the composable leaves the composition.See AfterHotReloadEffect for details.

AfterHotReloadEffect

Composable alternative with automatic cleanup

isHotReloadActive

Check if hot reload is enabled

Build docs developers (and LLMs) love