Status Update
Comments
di...@google.com <di...@google.com> #2
The rendering thread in the dump is (maybe) stuck in:
"Layoutlib Render Thread" #93 daemon prio=4 os_prio=31 cpu=513386.93ms elapsed=12944.64s tid=0x00007f8308a18000 nid=0x28e03 runnable [0x0000700008a79000]
java.lang.Thread.State: RUNNABLE
at android.graphics.HardwareRenderer.nSyncAndDrawFrame(Native Method)
at android.graphics.HardwareRenderer.syncAndDrawFrame(HardwareRenderer.java:456)
at android.graphics.HardwareRenderer$FrameRenderRequest.syncAndDraw(HardwareRenderer.java:425)
at com.android.layoutlib.bridge.impl.RenderSessionImpl.renderAndBuildResult(RenderSessionImpl.java:430)
at com.android.layoutlib.bridge.impl.RenderSessionImpl.renderAndBuildResult(RenderSessionImpl.java:576)
at com.android.layoutlib.bridge.impl.RenderSessionImpl.render(RenderSessionImpl.java:451)
at com.android.layoutlib.bridge.BridgeRenderSession.render(BridgeRenderSession.java:122)
Tor, did you have the embedded emulator also open?
am...@google.com <am...@google.com>
am...@google.com <am...@google.com> #3
It seems the issue is only reproducible when you try to drag out of the preview bounds. Dragging within the preview works for me. I guess Compose's DragGestureDetector
does not expect to receive an Offset
out of screen bounds. I'll dig deeper.
Exception in thread "Layoutlib Render Thread @coroutine#23" java.lang.NullPointerException
(Coroutine boundary)
at androidx.compose.foundation.gestures.DraggableKt$draggable$9$3$1$1.invokeSuspend(Draggable.kt:263)
at androidx.compose.foundation.gestures.ForEachGestureKt.forEachGesture(ForEachGesture.kt:41)
at androidx.compose.foundation.gestures.DraggableKt$draggable$9$3$1.invokeSuspend(Draggable.kt:262)
at androidx.compose.foundation.gestures.DraggableKt$draggable$9$3.invokeSuspend(Draggable.kt:261)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilterKt$pointerInput$6$2$1.invokeSuspend(SuspendingPointerInputFilter.kt:286)
Caused by: java.lang.NullPointerException
at androidx.compose.foundation.gestures.DragGestureDetectorKt.horizontalDrag-jO51t88(DragGestureDetector.kt:1204)
at androidx.compose.foundation.gestures.DragGestureDetectorKt$horizontalDrag$1.invokeSuspend(DragGestureDetector.kt)
at _layoutlib_._internal_.kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at _layoutlib_._internal_.kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
at _layoutlib_._internal_.kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
at _layoutlib_._internal_.kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:398)
at _layoutlib_._internal_.kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:432)
at _layoutlib_._internal_.kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:421)
at _layoutlib_._internal_.kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:329)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:511)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:407)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:420)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:284)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:271)
at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:151)
at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:88)
at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:80)
at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:918)
at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:900)
am...@google.com <am...@google.com> #4
Update: it's not the screen bounds, but the Composable bounds.
I deployed this preview to a device and tried to execute the same steps, but there is no crash in this scenario.
One thing that might also be relevant for the record: releasing the mouse inside the Composable causes a DragEvent$DragStop
event to be sent, while releasing it outside the composable will send a DragEvent$DragCancelled
. I'll investigate why sending the later causes the crash.
am...@google.com <am...@google.com> #5
PointerInputEventProcessor#process
(thanks Matvei for the pointers!) was receiving the drag event, but was returning ProcessResult(..., anyMovementConsumed = false)
for interactive preview, as opposed to ProcessResult(..., anyMovementConsumed = true)
for emulator or real device.
After some investigation to determine why dragging outside the composable was not consumed in interactive preview, I narrowed down the discrepancy to NodeParent#dispatchMainEventPass
in HitPathTracker.kt. In that function, the drag event was not dispatched in Interactive Preview, but was on the emulator, because the children
list was empty in the former and the latter had 1 child, for which a dispatch pass would happen.
The children
list was empty because the pointerIds
list of the parent node's only child was cleared in Node#cleanUpHits
, more specifically, if event.type == PointerEventType.Exit && !event.buttons.areAnyPressed
. This check was added when compose introduced support to mouse hover events in PointerEventType.Exit
, because on the emulator the type is PointerEventType.Exit
, as this is a continuation of a drag move, as opposed to a hover outside the composable bounds.
The event type is set to PointerEventType.Exit
in the buildCache()
method, because isCursorEvent(event)
returns true. That's odd, because we want to handle a touch event, not a mouse one. By digging deeper, I noticed that event.changes[0].type
was Touch
on the emulator and Unknown
when using the Interactive Preview.
The reason for that is we're not setting the MotionEvent
properly in layoutlib (thanks Jerome for the pointers). In RenderSessionImpl#dispatchTouchEvent
, we were creating the event using the default values for almost everything, except the action value, which we set to ACTION_DOWN
. As a result, the MotionEvent
toolType
was set to TOOL_TYPE_UNKNOWN
and that maps to PointerType.Unknown
in Compose.
Modifying RenderSessionImpl#dispatchTouchEvent
to set the MotionEvent
toolType
to TOOL_TYPE_FINGER
, which maps to PointerType.Touch
in Compose, seems to fix the issue. That's done in ag/16009077. This fix should be available in Bumblebee.
tn...@google.com <tn...@google.com> #6
Great sleuthing!
tn...@google.com <tn...@google.com> #7
By the way does this automatically get into Studio or does somebody have to check in layoutlib prebuilts at some point? I know where were some NPE fixes in layoutlib yesterday too for some other things I ran into.
Description
Using the Compose version of the Shrine app, interactive preview works really well for switching between the front page and the shopping cart view -- I can click back and forth. But if I start dragging one of the items in the gallery to the left, the view correct scrolls as expected -- but when I finish the drag, the preview stops being interactive -- I can't scroll again and I can't open the shopping cart view again.
I'm attaching a thread dump, though I can't spot anything obvious. And while the interactive preview stays unresponsive, the IDE remains responsive and I can click on the Stop button in the interactive preview, and then start it again and things are fine.