{"id":2651,"date":"2022-07-28T11:35:34","date_gmt":"2022-07-28T18:35:34","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/surface-duo\/?p=2651"},"modified":"2022-07-28T11:35:34","modified_gmt":"2022-07-28T18:35:34","slug":"jetpack-compose-drag-and-drop","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/surface-duo\/jetpack-compose-drag-and-drop\/","title":{"rendered":"Drag and drop library for Jetpack Compose"},"content":{"rendered":"<p>\n  Hello Compose developers!\n<\/p>\n<p>\n  This week, we are excited to announce the release of <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-sdk\/tree\/main\/DragAndDrop\">DragAndDrop<\/a>, a new utility library that will help you easily add interactive drag and drop capabilities to your app in Compose, following Google\u2019s <a href=\"https:\/\/developer.android.com\/guide\/topics\/ui\/drag-drop\">Drag and drop<\/a> guidelines. It is the newest addition to Microsoft\u2019s <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-sdk\">Compose SDK<\/a>. \n<\/p>\n<p>\n  On dual-screen and foldable devices, one of the advantages of running multiple apps across screens is to share information between apps easily. To achieve this, drag and drop is definitely a feature that developers should think about bringing into their application. \n<\/p>\n<h2>Overview<\/h2>\n<p>\n  The <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-sdk\/tree\/main\/DragAndDrop\">DragAndDrop<\/a> library provides a drag container which represents the draggable area with long press gesture. The drag target wraps the drag data with a shadow during the dragging process. A drop container includes a dropping area to handle the detected drag data. Currently it supports image and text MIME types. \n<\/p>\n<h3>Drag and drop process<\/h3>\n<ol>\n<li>\n  Wrapping your Composable with <code>DragContainer<\/code> to define a draggable area where the drag and drop happens.<\/p>\n<pre>@Composable\r\nfun DragContainer(\r\n    modifier: Modifier = Modifier,\r\n    content: @Composable BoxScope.() -&gt; Unit\r\n)<\/pre>\n<\/li>\n<li>\n  Create <code>DragData<\/code> to represent the data to share with the metadata, including <code>MimeType<\/code>.<\/p>\n<pre>class DragData(\r\n    val type: MimeType = MimeType.<em>TEXT_PLAIN<\/em>,\r\n    val data: Any? = null\r\n)\r\n\r\nenum class MimeType(val value: String) {\r\n    IMAGE_JPEG(\"image\/jpeg\"),\r\n    TEXT_PLAIN(\"text\/plain\"),\r\n    UNKNOWN_TYPE(\"unknown\")\r\n}<\/pre>\n<\/li>\n<li>\n  Initialize a <code>DragTarget<\/code> with the created <code>DragData<\/code> and the Composable which would respond to the user&#8217;s drag gesture by long pressing. A shadow will be created for the target during the dragging process.<\/p>\n<pre>@Composable\r\nfun DragTarget(\r\n    dragData: DragData,\r\n    content: @Composable (() -&gt; Unit)\r\n)<\/pre>\n<\/li>\n<li>\n  Specify a Composable to handle the dropping event using <code>DropContainer<\/code>. The lambda <code>onDrag<\/code> will let you know whether the dragging gesture goes within the dropping area and the gesture finishes.<\/p>\n<pre>@Composable\r\nfun DropContainer(\r\n    modifier: Modifier,\r\n    onDrag: (inBounds: Boolean, isDragging: Boolean) -&gt; Unit,\r\n    content: @Composable (BoxScope.(data: DragData?) -&gt; Unit)\r\n)<\/pre>\n<\/li>\n<\/ol>\n<h2>Get started<\/h2>\n<p>Follow these steps to add the drag and drop library to your project:<\/p>\n<ol>\n<li>\n  Make sure you have <code>mavenCentral()<\/code> repository in your top level <strong>build.gradle<\/strong> file:<\/p>\n<pre>allprojects {\r\n    repositories {\r\n        google()\r\n        mavenCentral()\r\n    }\r\n}<\/pre>\n<\/li>\n<li>\n  Add dependencies to the module-level <strong>build.gradle<\/strong> file (current version may be different from what&#8217;s shown here).<\/p>\n<pre>Implementation \"com.microsoft.device.dualscreen:draganddrop:1.0.0-alpha01\"<\/pre>\n<\/li>\n<li>\n  Also ensure the <code>compileSdkVersion<\/code> and <code>targetSdkVersion<\/code> are set to API 31 or newer in the module-level <strong>build.gradle<\/strong> file.<\/p>\n<pre>android {\r\n    defaultConfig {\r\n        targetSdkVersion 31\r\n    }\r\n...\r\n}<\/pre>\n<\/li>\n<\/ol>\n<p>\nFor more detailed instructions and API reference information, please refer to the <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-sdk\/tree\/main\/DragAndDrop#readme\">DragAndDrop README<\/a>.\n<\/p>\n<h2>Example usage<\/h2>\n<p>\n  To see some examples of how to use this library, you can check out a few different samples, such as <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\/tree\/main\/DragAndDrop\">DragAndDrop sample<\/a> in our <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\">Compose sample<\/a> repo.\n<\/p>\n<p>\n  In the SDK repo, we created a <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-sdk\/tree\/main\/DragAndDrop\/sample\">small sample<\/a> to showcase how to build the drag and drop feature using our library in only four steps as outlined above. \n<\/p>\n<p>\n  Before building the drag and drop feature, we need to decide where and how big the drag and drop area is. It could be part of your application, or the whole display area of the app. In this sample, we define the whole display area as the draggable area by using <code>DragContainer<\/code> mentioned in the first step of the building process. \n<\/p>\n<p>\n  For the drag part, firstly it is the <code>DragData\n<\/code>, which represents the data the application allows sharing via the drag and drop feature. With our component, currently the app can share both text and image content using <code>MimeType<\/code>. In our sample, we create one text <code>DragData<\/code> and one image <code>DragData<\/code>, which is the second step.\n<\/p>\n<p>\n  The third step is to create <code>DragTarget<\/code>, which is the <code>Composable<\/code> the user can drag and move around by long pressing gesture. It could be any kind of <code>Composable<\/code>, a <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/package-summary#Box(androidx.compose.ui.Modifier,androidx.compose.ui.Alignment,kotlin.Boolean,kotlin.Function1)\">Box,<\/a> a <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/package-summary#Column(androidx.compose.ui.Modifier,androidx.compose.foundation.layout.Arrangement.Vertical,androidx.compose.ui.Alignment.Horizontal,kotlin.Function1)\">Column<\/a>, <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/material\/package-summary#Text(kotlin.String,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.font.FontStyle,androidx.compose.ui.text.font.FontWeight,androidx.compose.ui.text.font.FontFamily,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextDecoration,androidx.compose.ui.text.style.TextAlign,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextOverflow,kotlin.Boolean,kotlin.Int,kotlin.Function1,androidx.compose.ui.text.TextStyle)\">Text<\/a>, or an <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/material\/package-summary#Text(kotlin.String,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.font.FontStyle,androidx.compose.ui.text.font.FontWeight,androidx.compose.ui.text.font.FontFamily,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextDecoration,androidx.compose.ui.text.style.TextAlign,androidx.compose.ui.unit.TextUnit,androidx.compose.ui.text.style.TextOverflow,kotlin.Boolean,kotlin.Int,kotlin.Function1,androidx.compose.ui.text.TextStyle)\">Image<\/a>. During the drag and drop, the component will create a shadow of the <code>DragTarget<\/code> composable. In our sample, the text box \u201cDrag me\u201d is the <code>DragTarget<\/code>, which is a box with text inside. And the text is the <code>DragData<\/code>. The shadow is for the whole text box, but only the text is the content we are sharing.\n<\/p>\n<p>\n  Then for the drop part, we will handle the drop event and save the sharing content. The <code>DropContainer<\/code> provides a lambda <code>onDrag<\/code> to return whether the dragging gesture goes within the dropping area and the dragging gesture finishes. You could update your UI to tell the user where or when to drop the content. In the sample, we define four states and two lambdas to update UI to handle both text and image sharing at the same time. \n<\/p>\n<pre>var dragText by remember { mutableStateOf&lt;String?&gt;(null) }\r\nvar dragImage by remember { mutableStateOf&lt;Painter?&gt;(null) }\r\nval updateDragText: (String?) -&gt; Unit = { newValue -&gt; dragText = newValue }\r\nval updateDragImage: (Painter?) -&gt; Unit = { newValue -&gt; dragImage = newValue }\r\nvar isDroppingItem by remember { mutableStateOf(false) }\r\nvar isItemInBounds by remember { mutableStateOf(false) }<\/pre>\n<p>\n  We change the background color of the drop box based on the dragging event. The drop box is white by default. Once the dragging event starts, the drop box would change to light purple color to tell the user where to go. When the drag gesture enters the bounds of the drop box, the \u201c<strong>boxColor<\/strong>&#8221; would change again to dark purple. This lets the user know where they can drop the content to save it. Then the application can handle the content based on the \u201c<strong>dragData<\/strong>\u201d, to either update the UI or save the content.\n<\/p>\n<pre>DropContainer(\r\n    modifier = modifier,\r\n    onDrag = { inBounds, isDragging -&gt;\r\n        isDroppingItem = isDragging\r\n        isItemInBounds = inBounds\r\n    },\r\n) { dragData -&gt;\r\n    val boxColor = if (isDroppingItem &amp;&amp; isItemInBounds) Purple200 else if (isDroppingItem) Purple100 else Color.White\r\n...\r\n        dragData?.let {\r\n            if (!isDroppingItem) {\r\n                if (dragData.type == MimeType.TEXT_PLAIN) {\r\n                    dragText = dragData.data as String\r\n                }\r\n                if (dragData.type == MimeType.IMAGE_JPEG) {\r\n                    dragImage = dragData.data as Painter\r\n                }\r\n            }\r\n        }\r\n...\r\n    }\r\n}<\/pre>\n<p>\n  Our sample works in both the single-screen mode and dual-screen mode inside one application. But you can also build the drag and drop feature across two applications.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"600\" height=\"426\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2022\/07\/word-image-2651-2.gif\" class=\"wp-image-2653\" alt=\"Animation of the drag and drop sample app on the left screen of the Surface Duo emulator\" \/><\/br><em>Figure 1. Animation showing drag and drop in single-screen mode<\/em>\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"600\" height=\"425\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2022\/07\/word-image-2651-3.gif\" class=\"wp-image-2654\" alt=\"Animation of the drag and drop sample app on both screens of the Surface Duo emulator\" \/><br\/><em>Figure 2. Animation showing drag and drop when spanned across two screens<\/em>\n<\/p>\n<h2><strong>Resources and feedback<\/strong><\/h2>\n<p>\n  Check out the <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/android\/jetpack\/compose\">Surface Duo Jetpack Compose developer documentation<\/a> and <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/\">past blog posts<\/a> for links and details on all our samples. You can find our <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\">Jetpack Compose samples<\/a> on GitHub.\n<\/p>\n<p>\n  If you have any questions, or would like to tell us about your apps, use the <a href=\"http:\/\/aka.ms\/SurfaceDuoSDK-Feedback\">feedback forum<\/a> or message us on Twitter <a href=\"https:\/\/twitter.com\/surfaceduodev\">@surfaceduodev<\/a>.\n<\/p>\n<p>\n  Finally, join us <a href=\"https:\/\/twitch.tv\/surfaceduodev\">live on Twitch<\/a> at 11am PST to discuss the drag and drop feature, Jetpack Compose, and any other Surface Duo developer questions you might have.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello Compose developers! This week, we are excited to announce the release of DragAndDrop, a new utility library that will help you easily add interactive drag and drop capabilities to your app in Compose, following Google\u2019s Drag and drop guidelines. It is the newest addition to Microsoft\u2019s Compose SDK. On dual-screen and foldable devices, one [&hellip;]<\/p>\n","protected":false},"author":30456,"featured_media":2661,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[365,717,692],"class_list":["post-2651","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-surface-duo-sdk","tag-android-developer","tag-foldable","tag-jetpack-compose"],"acf":[],"blog_post_summary":"<p>Hello Compose developers! This week, we are excited to announce the release of DragAndDrop, a new utility library that will help you easily add interactive drag and drop capabilities to your app in Compose, following Google\u2019s Drag and drop guidelines. It is the newest addition to Microsoft\u2019s Compose SDK. On dual-screen and foldable devices, one [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/2651","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/users\/30456"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/comments?post=2651"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/2651\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media\/2661"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media?parent=2651"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/categories?post=2651"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/tags?post=2651"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}