Skip to content

Limitations ​

Since 2.2.44

This page lists where ldlib2-compose differs from Jetpack Compose, and the side and lifecycle rules that are easy to break.

Side and threading ​

The composition runs on the client render thread only

On a dedicated server, and on the integrated server's own thread in singleplayer (where a menu's createUI also runs), ComposeElement.setContent only records the content. The server half of a menu gets its data through ordinary bindings.

Coroutines started by effects (LaunchedEffect, rememberCoroutineScope) are resumed on the render thread, once per frame. Code after delay() therefore runs on the render thread too.

Anything that registers a sync value, an RPC event or a menu slot must be created in createUI, outside the composition. See State and Sync.

Lifecycle ​

EventWhat happens to the composition
The element leaves its UI, or the screen closesThe composition is disposed. remember state is lost; rememberSaveable state is kept only with a stateKey.
A menu screen is covered by a recipe viewer such as JEI, then comes backSame as above: disposed and rebuilt. Use rememberSaveable with a stateKey for state that must survive.
An editor window is minimised and restoredNothing is lost. The element tree moves to a new ModularUI and the composition moves with it.
The content throws while composingThe error is logged and the element stays empty until the next setContent.

Avoid these

  • Do not create a ComposeElement per frame, for example in a tooltip component. A composition only releases the global snapshot system when its element is removed from a UI, and a tooltip's UI is dropped without removing anything.
  • Do not throw from onDispose. Compose stops at the first cleanup that throws, so later onDispose blocks, including this module's own unsubscriptions, never run.

Differences from Jetpack Compose ​

AreaBehaviour here
RuntimeJetBrains Compose runtime 1.8.2 only. No compose.ui, no compose.foundation.
StylingNamed parameters instead of a Modifier chain. Style properties have no order and are written through, never undone. Use classes for conditional appearance.
BlocksA block that reads state re-runs on its own, but it can only change properties. Values that decide what is composed must be read in the function body.
GesturesLDLib2 UI events with bubbling and drag-and-drop (DragHandler). No pointerInput, touch slop, fling or gesture detectors.
AnimationThe module's own implementation. AnimationSpec is TweenSpec or SpringSpec; only Float, Int and ARGB colour values. animateFloatAsState defaults to a 250 ms linear tween, not a spring.
AnimatedVisibilityFades only by default. Size changes are single-axis, and enter and exit must share the axis.
rememberSaveableIn memory only: survives a closed screen (with stateKey), scrolled-out rows and removed tabs. Cleared on leaving a world; never survives a restart.
Lazy listsitems is compared by identity. Fixed row sizes must actually be applied to the rows. Grid column widths are fixed.
Grid cellsA cell moved into another row by an edit loses its state.
SplitViewOnly the initial divider position (initialPercentage) can be set; dragging reports nothing.
SceneThe camera belongs to the widget, not to composition state.
AccessibilityComposed elements contribute nothing to Minecraft's narrator.

Development environment ​

IntelliJ coroutine debug agent

An IllegalAccessError mentioning DebugProbesKt and DebugProbesImpl on the first Compose screen comes from IntelliJ's coroutine debug agent. Turn off the coroutine agent in the IDE settings, or launch with gradlew runClient. It cannot happen outside a development environment.

Released under the MIT License.