
Experimental features often suffer from documentation lag. Often the tutorials and documentation lag behind the releases. Drag and drop is one such example. Frustrating as it might seem, it’s also an opportunity to see how the feature evolves and to hone some skills in problem solving. Although this article will contain code snippets that may or may not help, the theme is to help you to think differently and solve the problem yourself for the next time that you experience documentation lag.
The code and context of this article is: compose.ui version 1.10.1.
The official documentation at kotlinlang.org/docs/multiplatform/compose-drag-drop.html serves as a starting guide, but you’ll quickly note that it simply doesn’t compile.
When the provided code snippets fail to compile, the key is to dive into the function signatures rather than hunting for copy-paste solutions. Begin by examining the two functions that are most important: `Modifier.dragAndDropSource` and `Modifier.dragAndDropTarget`. The former takes a `drawDragDecoration: DrawScope.() -> Unit` for rendering the drag ghost (like a simple `drawRect` with `drawText` inside) and a `transferData: (Offset) -> DragAndDropTransferData?` lambda.
This `transferData` lambda is where challenges emerge, as its trailing lambda calls functions like `detectDragGestures` and a non-existent `startTransfer`. It looks complicated, but what is it trying to achieve? The name gives it away — it’s trying to generate DragAndDropTransferData — information to give to whatever control is lucky enough to get dropped on.
Take a look at `DragAndDropTransferData` — it’s an `expect` class without a public constructor… this is a hint that the MP part of KMP is not ready here and we need platform-specific `actual` implementations. Prioritize JVM as the reference platform, where you’ll find a constructor that will look much like the example, holding a `transferable` (akin to Android’s Bundle for primitives), `supportedActions`, `dragDecorationOffset`, and `onTransferCompleted`. So how do we get this back into our commonMain code?
expect fun createTransferData(offset: Offset): DragAndDropTransferData
Use the hint menu to generate the `actual` classes, beginning with JVM. In the JVM actual, replicate the bundle-like transferable for your data, such as a string payload. Test iteratively: drag from source, verify the ghost renders, and confirm transfer data creation without crashes.
Shifting to the target side, `Modifier.dragAndDropTarget` requires a `shouldStartDragAndDrop: (DragAndDropEvent) -> Boolean` and a `target: DragAndDropTarget`. Again, `DragAndDropEvent` is expect-only, so craft platform-specific accessors to extract your payload. Define a custom `expect` data class like `MyDragEvent(val text: String)` and implement `actual fun DragAndDropEvent.getData():MyDragEvent` per platform — JVM first, pulling from the transferable.
data class MyDragEvent(val text: String)
expect fun DragAndDropEvent.getData(): MyDragEvent?
In `onDrop`, validate the action (eg. copy or move), retrieve your `MyDragEvent`, and apply it to the target’s state, like updating a list or text field. Debug by logging offsets and actions across platforms, adjusting for quirks like iOS gesture handling or JS pointer events.
The process demands resilience: expect changes with each compose.ui update, so favor understanding signatures and expect/actual patterns over rigid code. Prototype on JVM, port to Android/JS/iOS, and refine gestures through trial — this mindset scales beyond drag and drop to any experimental KMP UI feature.
Thanks for reading!
How to implement Drag and Drop in Kotlin Multiplatform was originally published in Google Developer Experts on Medium, where people are continuing the conversation by highlighting and responding to this story.