PC パソコン

How to Fix “@Composable invocations can only happen from the context of a @Composable function”

When developing a Jetpack Compose UI, you may encounter the compiler error “@Composable invocations can only happen from the context of a @Composable function”.

@Composable invocations can only happen from the context of a @Composable function

This error means that a Composable function is being called from normal Kotlin code that is not currently executing in a Compose Composition.

Typical examples include calling Text() from a normal function, calling a Composable from a Button onClick callback, using remember inside an event handler, or trying to invoke LaunchedEffect after a click.

Compose APIs such as remember, LaunchedEffect, and rememberCoroutineScope are connected to the Composition and its lifecycle, so it is important to distinguish Composable execution from normal Kotlin callbacks. :contentReference[oaicite:15]{index=15}

This article explains why the error occurs and how to fix the most common cases correctly.

  1. What Does the Error Mean?
  2. Common Situations
  3. Cause 1: The Calling Function Is Not @Composable
  4. Solution 1: Add @Composable to UI Functions
  5. Do Not Make Every Function @Composable
  6. Cause 2: Calling a Composable Inside onClick
  7. Solution 2: Change State Instead
  8. Why Can’t Text() Be Called from onClick?
  9. Cause 3: Calling LaunchedEffect Inside onClick
  10. Solution 3: Use rememberCoroutineScope for Click Events
  11. LaunchedEffect vs rememberCoroutineScope
  12. Correct LaunchedEffect Example
  13. Correct rememberCoroutineScope Example
  14. Cause 4: Calling remember Inside an Event Callback
  15. Solution 4: Call remember in the Composable Body
  16. Cause 5: Confusing Normal and Composable Lambdas
  17. Solution 5: Annotate the Lambda Type
  18. Composable Lambda with a Parameter
  19. Cause 6: Calling a Composable from a ViewModel
  20. Solution 6: Let the ViewModel Expose State
  21. Cause 7: Calling Compose UI from a Repository or UseCase
  22. Cause 8: Executing a Composable from a Normal Function
  23. Cause 9: Calling Compose UI Outside setContent
  24. Cause 10: Assuming if Statements Cannot Contain Composables
  25. when Can Also Contain Compose UI
  26. Showing a Dialog After a Click
  27. Showing a Snackbar from an Event
  28. Running Work When the Screen Appears
  29. Running Work When a Value Changes
  30. Do Not Call SideEffect from onClick
  31. DisposableEffect Also Belongs in a Composable Context
  32. Common Mistake: Creating a Screen Directly from onClick
  33. Common Mistake: Reading Compose-Specific Values from a Normal Function
  34. Common Mistake: Giving the ViewModel UI Instead of State
  35. When Adding @Composable Is the Correct Fix
  36. When You Should Not Add @Composable
  37. Trace the Call Chain
  38. Remember That Compose Is Recomposition-Based
  39. Fastest Troubleshooting Order
  40. Quick Fix by Situation
  41. If the Error Still Occurs
  42. Summary

What Does the Error Mean?

Jetpack Compose uses functions annotated with @Composable to describe UI.

@Composable fun Greeting() { Text("Hello") }

Text() is itself a Composable function and must therefore be invoked from a valid Composable context.

This is invalid:

fun showGreeting() { Text("Hello") }

showGreeting() is a normal Kotlin function, so the compiler reports:

@Composable invocations can only happen from the context of a @Composable function

Basic rule: Compose UI APIs such as Text, Button, Column, remember, and LaunchedEffect must be invoked from an appropriate Composable context.

Common Situations

  • Calling Text() from a normal Kotlin function
  • Calling a custom Composable from a non-Composable function
  • Calling a Composable inside onClick
  • Calling remember inside an event callback
  • Calling LaunchedEffect inside onClick
  • Calling a Composable from a ViewModel
  • Calling a Composable from a Repository or UseCase
  • Passing Compose UI through a normal () -> Unit lambda
  • Forgetting @Composable on a lambda parameter
  • Trying to execute Compose UI outside setContent

Cause 1: The Calling Function Is Not @Composable

This code is invalid:

fun UserName(name: String) { Text(text = name) }

Because the function builds Compose UI, declare it as Composable:

@Composable fun UserName(name: String) { Text(text = name) }

Solution 1: Add @Composable to UI Functions

@Composable fun ProfileScreen() { Text("Profile") }

A parent Composable can then invoke it:

@Composable fun AppScreen() { ProfileScreen() }

Do Not Make Every Function @Composable

A function that only performs calculations or business logic should normally remain plain Kotlin.

fun calculateTotal( price: Int, count: Int ): Int { return price * count }

Use the result from a Composable:

@Composable fun PriceScreen( price: Int, count: Int ) { val total = calculateTotal(price, count)
Text("Total: $total")

}

Design principle: Keep UI declaration in Composables and keep business logic, calculations, and data access in normal Kotlin whenever possible.

Cause 2: Calling a Composable Inside onClick

This is a very common mistake:

@Composable fun SampleScreen() { Button( onClick = { Text("Clicked") } ) { Text("Button") } }

The onClick block is a normal event callback, not a Composable content block.

Solution 2: Change State Instead

@Composable fun SampleScreen() { var clicked by remember { mutableStateOf(false) }
Column {
    Button(
        onClick = {
            clicked = true
        }
    ) {
        Text("Button")
    }

    if (clicked) {
        Text("Clicked")
    }
}

}

Compose state changes schedule recomposition of Composables that read that state. Google’s current documentation explains that remember stores values in the Composition and that mutableStateOf creates observable state integrated with Compose. :contentReference[oaicite:16]{index=16}

Why Can’t Text() Be Called from onClick?

Compose does not use an imperative model where an event handler manually inserts another UI element.

Instead, state determines what UI should currently exist.

clicked = false → Do not show the message

clicked = true
→ Show the message

The event changes the state, and Compose updates the UI from that state.

Cause 3: Calling LaunchedEffect Inside onClick

This is also invalid:

@Composable fun SampleScreen() { Button( onClick = { LaunchedEffect(Unit) { // Suspended work } } ) { Text("Start") } }

LaunchedEffect is itself a Composable function.

Google’s current side-effect documentation explicitly notes that LaunchedEffect is a Composable and must be used from other Composable functions. :contentReference[oaicite:17]{index=17}

Solution 3: Use rememberCoroutineScope for Click Events

@Composable fun SampleScreen() { val scope = rememberCoroutineScope()
Button(
    onClick = {
        scope.launch {
            // Call suspend function
        }
    }
) {
    Text("Start")
}

}

Google recommends rememberCoroutineScope when a coroutine needs to be launched from an event handler. The returned scope is associated with the location in the Composition and is cancelled when that call leaves the Composition. :contentReference[oaicite:18]{index=18}

LaunchedEffect vs rememberCoroutineScope

Composable enters the Composition A key or state value changes → LaunchedEffect

User presses a button
An event callback runs
→ rememberCoroutineScope + launch

LaunchedEffect starts a coroutine when it enters the Composition. If its keys change, the previous coroutine is cancelled and a new one starts. The coroutine is also cancelled when the effect leaves the Composition. :contentReference[oaicite:19]{index=19}

Correct LaunchedEffect Example

@Composable fun UserScreen( userId: String ) { LaunchedEffect(userId) { loadUser(userId) }
Text("User")

}

Correct rememberCoroutineScope Example

@Composable fun SaveButton() { val scope = rememberCoroutineScope()
Button(
    onClick = {
        scope.launch {
            saveData()
        }
    }
) {
    Text("Save")
}

}

Cause 4: Calling remember Inside an Event Callback

This is incorrect:

@Composable fun SampleScreen() { Button( onClick = { val state = remember { mutableStateOf(false) } } ) { Text("Button") } }

remember stores data in the Composition and therefore needs a Composable context.

Google’s state documentation explains that values created with remember are stored during the initial Composition and returned again during recomposition. :contentReference[oaicite:20]{index=20}

Solution 4: Call remember in the Composable Body

@Composable fun SampleScreen() { var enabled by remember { mutableStateOf(false) }
Button(
    onClick = {
        enabled = true
    }
) {
    Text("Enable")
}

if (enabled) {
    Text("Enabled")
}

}

Cause 5: Confusing Normal and Composable Lambdas

A normal lambda type is:

() -> Unit

A lambda intended to contain Compose UI is:

@Composable () -> Unit

Solution 5: Annotate the Lambda Type

Problematic example:

@Composable fun Container( content: () -> Unit ) { content() }

If content contains Compose UI:

@Composable fun Container( content: @Composable () -> Unit ) { content() }

Usage:

@Composable fun Screen() { Container { Text("Hello") } }

Composable Lambda with a Parameter

@Composable fun UserContainer( content: @Composable (String) -> Unit ) { content("Alice") }

Usage:

@Composable fun Screen() { UserContainer { name -> Text(name) } }

Cause 6: Calling a Composable from a ViewModel

Avoid designs such as:

class MainViewModel : ViewModel() {
fun showScreen() {
    MyScreen()
}

}

A ViewModel is not a Compose rendering context.

Solution 6: Let the ViewModel Expose State

ViewModel:

class MainViewModel : ViewModel() {
private val _showMessage =
    MutableStateFlow(false)

val showMessage =
    _showMessage.asStateFlow()

fun onButtonClick() {
    _showMessage.value = true
}

}

Composable:

@Composable fun MainScreen( viewModel: MainViewModel ) { val showMessage by viewModel.showMessage.collectAsState()
Button(
    onClick = {
        viewModel.onButtonClick()
    }
) {
    Text("Show")
}

if (showMessage) {
    Text("Hello")
}

}

This keeps events and state changes outside the UI declaration while letting the Composable render the current state.

Cause 7: Calling Compose UI from a Repository or UseCase

This is also incorrect:

class UserRepository {
fun showError() {
    Text("Error")
}

}

Return data or an error result instead:

class UserRepository {
suspend fun loadUser():
    Result<User> {
    // Load data
}

}

The UI layer can then decide what to display.

Cause 8: Executing a Composable from a Normal Function

Problematic:

fun getContent() { Greeting() }

If you really need to return Compose content, return a Composable lambda:

fun getContent(): @Composable () -> Unit = { Greeting() }

Cause 9: Calling Compose UI Outside setContent

In a Compose Activity, setContent establishes the UI Composition.

class MainActivity : ComponentActivity() {
override fun onCreate(
    savedInstanceState: Bundle?
) {
    super.onCreate(savedInstanceState)

    setContent {
        App()
    }
}

}

If App() is Composable, invoke it inside the Compose content block.

Cause 10: Assuming if Statements Cannot Contain Composables

Normal control flow is valid inside Composable functions.

@Composable fun Screen( loading: Boolean ) { if (loading) { Text("Loading...") } else { Text("Completed") } }

The problem is not the if statement. The important question is whether the call site is in a Composable context.

when Can Also Contain Compose UI

@Composable fun ResultScreen( state: UiState ) { when (state) { UiState.Loading -> { Text("Loading") }
    UiState.Success -> {
        Text("Success")
    }

    UiState.Error -> {
        Text("Error")
    }
}

}

Showing a Dialog After a Click

Do not do this:

Button( onClick = { AlertDialog(...) } )

Change state instead:

@Composable fun DialogSample() { var showDialog by remember { mutableStateOf(false) }
Button(
    onClick = {
        showDialog = true
    }
) {
    Text("Open")
}

if (showDialog) {
    AlertDialog(
        onDismissRequest = {
            showDialog = false
        },
        confirmButton = {
            TextButton(
                onClick = {
                    showDialog = false
                }
            ) {
                Text("OK")
            }
        },
        text = {
            Text("Message")
        }
    )
}

}

Showing a Snackbar from an Event

SnackbarHostState.showSnackbar() is a suspend function, so a composition-aware CoroutineScope is useful.

@Composable fun SnackbarSample() { val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()

Scaffold(
    snackbarHost = {
        SnackbarHost(snackbarHostState)
    }
) {
    Button(
        onClick = {
            scope.launch {
                snackbarHostState
                    .showSnackbar("Saved")
            }
        }
    ) {
        Text("Save")
    }
}

}

Google’s side-effect documentation uses the same pattern: create a scope with rememberCoroutineScope and launch from the button event handler. :contentReference[oaicite:21]{index=21}

Running Work When the Screen Appears

If the operation should run because a Composable entered the Composition rather than because the user clicked a button, use LaunchedEffect.

@Composable fun ProfileScreen( viewModel: ProfileViewModel ) { LaunchedEffect(Unit) { viewModel.loadProfile() }
Text("Profile")

}

When LaunchedEffect enters the Composition it launches its coroutine, and that coroutine is cancelled when the effect leaves the Composition. :contentReference[oaicite:22]{index=22}

Running Work When a Value Changes

@Composable fun UserScreen( userId: String ) { LaunchedEffect(userId) { loadUser(userId) } }

If userId changes, the existing effect is cancelled and restarted with the new key. :contentReference[oaicite:23]{index=23}

Do Not Call SideEffect from onClick

SideEffect is also a Composable API.

This is invalid:

Button( onClick = { SideEffect { // Work } } )

Google describes SideEffect as an effect that runs after every successful recomposition, commonly for publishing Compose state to non-Compose code. :contentReference[oaicite:24]{index=24}

DisposableEffect Also Belongs in a Composable Context

@Composable fun LifecycleSample( lifecycleOwner: LifecycleOwner ) { DisposableEffect(lifecycleOwner) {
    val observer =
        LifecycleEventObserver { _, _ ->
            // Work
        }

    lifecycleOwner.lifecycle
        .addObserver(observer)

    onDispose {
        lifecycleOwner.lifecycle
            .removeObserver(observer)
    }
}

}

Google documents DisposableEffect for effects that require cleanup when keys change or when the Composable leaves the Composition. :contentReference[oaicite:25]{index=25}

Common Mistake: Creating a Screen Directly from onClick

Incorrect:

Button( onClick = { DetailScreen() } )

For navigation, invoke a normal navigation action instead:

Button( onClick = { navController.navigate("detail") } )

Common Mistake: Reading Compose-Specific Values from a Normal Function

A normal function should not directly access APIs that require a Composable context.

For example, instead of:

fun getColor(): Color { return MaterialTheme.colorScheme.primary }

read the value in Compose and pass it to normal code:

@Composable fun Screen() { val color = MaterialTheme.colorScheme.primary
Content(color)

}

fun Content(color: Color) {
// Normal Kotlin logic
}

Common Mistake: Giving the ViewModel UI Instead of State

Prefer state such as:

data class UiState( val loading: Boolean = false, val message: String? = null )

and let the Composable decide what UI corresponds to that state.

When Adding @Composable Is the Correct Fix

If the function’s job is to describe UI:

fun Title() { Text("Title") }

change it to:

@Composable fun Title() { Text("Title") }

When You Should Not Add @Composable

Business logic does not need the annotation.

fun validateName( name: String ): Boolean { return name.isNotBlank() }

Keep such functions as normal Kotlin code.

Trace the Call Chain

This call chain is valid:

@Composable Screen() ↓ @Composable Content() ↓ @Composable UserCard() ↓ Text()

This one crosses into a normal callback:

@Composable Screen() ↓ onClick { ... } ↓ Text()

The second call to Text() is therefore invalid.

Remember That Compose Is Recomposition-Based

Composable functions can run again when state changes.

Google defines Composition as the description of the UI produced by executing Composables, and recomposition as re-running Composables to update that Composition when data changes. :contentReference[oaicite:26]{index=26}

That is why event handlers should normally update state instead of directly invoking additional UI functions.

Fastest Troubleshooting Order

  1. Check whether the function containing the error is @Composable.
  2. Check whether the called API is a Composable.
  3. Check whether the call occurs inside onClick or another normal callback.
  4. If it changes UI, represent the change as state.
  5. If it launches suspended work, choose between LaunchedEffect and rememberCoroutineScope.
  6. If a lambda contains Compose UI, make it @Composable () -> Unit.
  7. Make sure ViewModels and repositories do not directly invoke Compose UI.

Quick Fix by Situation

Text() called from a normal UI function → Add @Composable to that UI function

Text() called from onClick
→ Change state and render Text from the Composable

LaunchedEffect called from onClick
→ Use rememberCoroutineScope + launch

Composable passed to a normal lambda
→ Change the type to @Composable () -> Unit

Composable called from ViewModel
→ Update state in the ViewModel and render in Compose

Suspend work when the screen enters Composition
→ Use LaunchedEffect

If the Error Still Occurs

Check all of the following:

  • The calling UI function has @Composable.
  • Text, Button, or Column is not being called from normal Kotlin code.
  • remember is not called inside an event handler.
  • LaunchedEffect is not called inside onClick.
  • SideEffect and DisposableEffect are not called from normal callbacks.
  • Composable lambda parameters use @Composable.
  • ViewModels do not directly call Compose UI.
  • Repositories do not directly call Compose UI.
  • UI changes are represented with state.
  • Coroutines are launched with the appropriate Compose API.

Summary

Jetpack Compose “@Composable invocations can only happen from the context of a @Composable function” means that a Composable function is being invoked from a location that is not a valid Composable context.

If a function’s responsibility is UI declaration, annotate it correctly:

@Composable fun Greeting() { Text("Hello") }

However, do not directly invoke Composables from callbacks such as onClick.

Instead, change state:

onClick = { showMessage = true }

and render from the Composable:

if (showMessage) { Text("Message") }

This follows Compose’s state and recomposition model. :contentReference[oaicite:27]{index=27}

For suspended work triggered by an event, use:

val scope = rememberCoroutineScope()

Button(
onClick = {
scope.launch {
saveData()
}
}
) {
Text("Save")
}

Google’s current documentation recommends rememberCoroutineScope for launching coroutines from event handlers. :contentReference[oaicite:28]{index=28}

For work tied to entering the Composition or to key changes, use LaunchedEffect. It starts a coroutine when it enters the Composition, restarts when its keys change, and is cancelled when it leaves the Composition. :contentReference[oaicite:29]{index=29}

When this compiler error appears, the most useful question is not simply “Where should I add @Composable?” but “Is this code describing UI, handling an event, changing state, or running a side effect?” Once that distinction is clear, the correct fix is usually straightforward.