I'm new in Jetpack Compose, I'm trying to do a drag and drop screen with items to be moved from a column to another one. I took inspiration from this link and I creted the following files:
TaskColumns.kt
@Preview
@Composable
fun TaskColumns() {
var columnWidth by remember {
mutableStateOf(0.dp)
}
val density = LocalDensity.current
val items = remember {
mutableListOf(
mutableListOf(5,6,7,8),
mutableListOf(),
mutableListOf(),
mutableListOf())
}
Surface(
Modifier.fillMaxSize()
) {
LongPressDraggable {
Row(Modifier.padding(4.dp)) {
repeat(4){
val column = it
DropTarget<IntArray>(modifier = Modifier.weight(1f)) { a, data ->
val n = data?.get(0)
val i = data?.get(1)
val c = data?.get(2)
var color = if (a) Color.Green
else Color.Blue
if (i != null && n != null && c != null) {
if (c != column){
Log.d("DND", "${data[0]}, ${data[1]}")
items[column].add(n)
items[c].remove(n)
}
color = Color.Blue
}
Column(
Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(4.dp)
.background(color)
.onPlaced {
columnWidth = with(density) { it.size.width.toDp() }
}
) {
items[column].forEachIndexed { index, item ->
DragTarget(
modifier = Modifier,
dataToDrop = intArrayOf(item, index, column),
onDragCustomAction = {
Log.d("DND", "$item")
}
) {
Column(
Modifier
.size(columnWidth)
.aspectRatio(1f / 1f)
.background(Color.Black),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "$item", color = Color.White)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
}
}
}
}
DragAndDrop.kt
internal val LocalDragTargetInfo = compositionLocalOf { DragTargetInfo() }
@Composable
fun LongPressDraggable(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit
) {
val state = remember { DragTargetInfo() }
CompositionLocalProvider(
LocalDragTargetInfo provides state
) {
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.TopStart)
{
content()
if (state.isDragging) {
var targetSize by remember {
mutableStateOf(IntSize.Zero)
}
Box(modifier = Modifier
.graphicsLayer {
val offset = (state.dragPosition + state.dragOffset)
// scaleX = 1.3f
// scaleY = 1.3f
alpha = if (targetSize == IntSize.Zero) 0f else .9f
translationX = offset.x.minus(targetSize.width / 2)
translationY = offset.y.minus(targetSize.height / 2)
}
.onGloballyPositioned {
targetSize = it.size
}
) {
state.draggableComposable?.invoke()
}
}
}
}
}
@Composable
fun <T> DragTarget(
modifier: Modifier,
dataToDrop: T,
onDragCustomAction: ()->Unit = {},
onDragCancelCustomAction: ()->Unit = {},
content: @Composable (() -> Unit),
) {
var currentPosition by remember { mutableStateOf(Offset.Zero) }
val currentState = LocalDragTargetInfo.current
Box(modifier = modifier
.onGloballyPositioned {
currentPosition = it.localToWindow(Offset.Zero)
}
.pointerInput(Unit) {
detectDragGesturesAfterLongPress(onDragStart = {
currentState.dataToDrop = dataToDrop
currentState.isDragging = true
currentState.dragPosition = currentPosition + it
currentState.draggableComposable = content
}, onDrag = { change, dragAmount ->
change.consume()
currentState.dragOffset += Offset(dragAmount.x, dragAmount.y)
onDragCustomAction()
}, onDragEnd = {
currentState.isDragging = false
currentState.dragOffset = Offset.Zero
}, onDragCancel = {
onDragCancelCustomAction()
currentState.dragOffset = Offset.Zero
currentState.isDragging = false
})
}) {
content()
}
}
@Composable
fun <T> DropTarget(
modifier: Modifier,
content: @Composable() (BoxScope.(isInBound: Boolean, data: T?) -> Unit)
) {
val dragInfo = LocalDragTargetInfo.current
val dragPosition = dragInfo.dragPosition
val dragOffset = dragInfo.dragOffset
var isCurrentDropTarget by remember {
mutableStateOf(false)
}
Box(modifier = modifier.onGloballyPositioned {
it.boundsInWindow().let { rect ->
isCurrentDropTarget = rect.contains(dragPosition + dragOffset)
}
}) {
val data =
if (isCurrentDropTarget && !dragInfo.isDragging) dragInfo.dataToDrop as T? else null
content(isCurrentDropTarget, data)
}
}
internal class DragTargetInfo {
var isDragging: Boolean by mutableStateOf(false)
var dragPosition by mutableStateOf(Offset.Zero)
var dragOffset by mutableStateOf(Offset.Zero)
var draggableComposable by mutableStateOf<(@Composable () -> Unit)?>(null)
var dataToDrop by mutableStateOf<Any?>(null)
}
With this code I should be able to move a black square from a column to the other one. The problem is that if I move the first item for example, it works, but then if I try to move the second one (that now became the first in the list) the value displayed is the same of the previous item.
You can see an example in this gif:
The problem seems to be the value passed to dataToDrop in DragTarget doesn't update. What am I doing wrong?
I suspect that the error occurs as a result of the way how you store the items
data structure. In the official documentation they say:
Caution:
Using mutable objects such asArrayList<T>
ormutableListOf()
as state in Compose causes your users to see incorrect or stale data in your app. Mutable objects that are not observable, such as ArrayList or a mutable data class, are not observable by Compose and don't trigger a recomposition when they change.
Please try to use a mutableStateListOf()
instead:
val items = remember {
mutableStateListOf(
mutableStateListOf(5,6,7,8),
mutableStateListOf(),
mutableStateListOf(),
mutableStateListOf()
)
}
Additionally, you need to define a key
for the DragTarget
Composables like so:
items[column].forEachIndexed { index, item ->
key(item) {
DragTarget(
//...
) {
//...
}
}
}