{"id":1415,"date":"2021-03-25T12:11:17","date_gmt":"2021-03-25T19:11:17","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/surface-duo\/?p=1415"},"modified":"2021-05-21T10:23:01","modified_gmt":"2021-05-21T17:23:01","slug":"jetpack-compose-foldable-samples","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/surface-duo\/jetpack-compose-foldable-samples\/","title":{"rendered":"Jetpack Compose foldable and dual-screen development"},"content":{"rendered":"<p>\n  Hi Android developers,\n<\/p>\n<p>\n  We talked about using <a href=\"https:\/\/developer.android.com\/jetpack\/compose\/\">Jetpack Compose<\/a>, the new Android UI toolkit for Microsoft Surface Duo development in a <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/jetpack-compose-dual-screen-sample\/?WT.mc_id=blog-surfaceduoblog-joyl1216\">previous blog post<\/a>. Now, Jetpack Compose is in beta. Combined with the use of <a href=\"https:\/\/developer.android.com\/jetpack\/androidx\/releases\/window\">Jetpack Window Manager<\/a>, Jetpack Compose is more flexible for developing apps for dual-screen devices and even easier to extend to other foldable devices. You can check out the new <a href=\"https:\/\/codelabs.developers.google.com\/codelabs\/android-window-manager-dual-screen-foldables#0\">dual-screen Codelab<\/a> for using Jetpack Window Manager to learn more. Today, we would like to showcase some examples of dual-screen development using Jetpack Compose in different use cases. \n<\/p>\n<p>\n  When building or migrating applications for foldable and dual-screen devices, how to best leverage the dual-screen on these new form factors from a design perspective can be a challenge. We introduced <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/introduction\/?WT.mc_id=docs-surfaceduoblog-joyl1216#dual-screen-app-patterns\">five app design patterns<\/a> which can be used in some common scenarios to help you expand your imagination.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1408\" height=\"289\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image.png\" class=\"wp-image-1416\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image.png 1408w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-300x62.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-1024x210.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-768x158.png 768w\" sizes=\"(max-width: 1408px) 100vw, 1408px\" \/><br\/><em>Figure 1: dual-screen app patterns<\/em>\n<\/p>\n<h2>Extended Canvas<\/h2>\n<p>\n  The <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/design\/extended-canvas\/?WT.mc_id=docs-surfaceduoblog-joyl1216\">Extended Canvas<\/a> pattern is the simplest but still a powerful dual-screen pattern, which can be used in an application requiring a bigger canvas, such as a map, spreadsheet, or drawing. In an application with this pattern, zooming and dragging features are normally expected. In Jetpack Compose, the implementation of <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/gestures\/package-summary#multitouch\">gestures<\/a> is quite different from the traditional way in that no listener is required. \n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1195\" height=\"933\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-1.png\" class=\"wp-image-1417\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-1.png 1195w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-1-300x234.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-1-1024x799.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-1-768x600.png 768w\" sizes=\"(max-width: 1195px) 100vw, 1195px\" \/><br\/><em>Figure 2: Extended Canvas Compose code sample<\/em><\/p>\n<p>\n  In the <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\/tree\/main\/ComposeSamples\/ExtendedCanvas\">Extended Canvas Compose Sample<\/a>, <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/gestures\/package-summary#transformable\">transformable<\/a> modifier is used to detect panning and zooming changes. Additionally, <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/input\/pointer\/PointerInputScope.html\">PointerInputScope<\/a> with the helper function <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/gestures\/package-summary.html#detectdraggestures\">detectDragGestures<\/a> is used to control the whole zooming and dragging flow so we can handle zooming and dragging at the same time.\n<\/p>\n<pre>Modifier.graphicsLayer(\r\n        scaleX = maxOf(minScale, minOf(maxScale, scale)),\r\n        scaleY = maxOf(minScale, minOf(maxScale, scale)),\r\n    )\r\n    .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }\r\n    .transformable(state = state)\r\n    .pointerInput(Unit) {\r\n        detectDragGestures(\r\n            onDrag = { _, dragAmount ->\r\n                offset = offset.plus(dragAmount)\r\n            }\r\n        )\r\n    }<\/pre>\n<\/p>\n<h2>List-Detail<\/h2>\n<p>\n  The <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/design\/list-detail\/?WT.mc_id=docs-surfaceduoblog-joyl1216\">List-Detail<\/a> pattern includes a list view and a detailed view for content. When an item in the list is selected, the details pane is updated. This pattern is ideal for a wider viewing area and is frequently used for email, image gallery, address books, and so on. \n<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-list-detail.jpg\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-list-detail.jpg\" alt=\"Surface Duo showing list of images and a single image\" width=\"1195\" height=\"933\" class=\"alignnone size-full wp-image-1425\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-list-detail.jpg 1195w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-list-detail-300x234.jpg 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-list-detail-1024x799.jpg 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-list-detail-768x600.jpg 768w\" sizes=\"(max-width: 1195px) 100vw, 1195px\" \/><\/a><br\/><em>Figure 3: List-Detail Compose code sample<\/em><\/p>\n<p>\n  As we mentioned in the previous blog post, to layout two views to fit a dual-screen (left\/right) configuration, using the <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/RowScope#weight\">weight<\/a> modifier inside <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/RowScope\">RowScope<\/a> is an easy solution to accomplish this. As you can see, the value of the weight modifier is set to 1 for both of the two child elements, which means the parent <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/package-summary#row\">Row<\/a> will be divided in half which aligns the two columns equally within the same space.\n<\/p>\n<pre>Row(\r\n    modifier = Modifier.fillMaxSize(),\r\n    horizontalArrangement = Arrangement.spacedBy(20.dp)\r\n) {\r\n    ListViewSpanned(\r\n        modifier = Modifier\r\n            .fillMaxHeight()\r\n            .weight(1f)\r\n    )    \r\n    DetailView(\r\n        modifier = Modifier\r\n            .fillMaxHeight()\r\n            .weight(1f)\r\n    )   \r\n}<\/pre>\n<p>\n  When we build an application for foldable or dual-screen devices, single-screen scenario should always be kept in mind. For the application with a List-Detail pattern, the single-screen mode should provide a navigation between the <code>list<\/code> view and <code>detail<\/code> view. The Navigation component provides support for <a href=\"https:\/\/developer.android.com\/jetpack\/compose\">Jetpack Compose<\/a> applications with the following dependencies.\n<\/p>\n<pre>dependencies {\r\n      implementation \"androidx.navigation:navigation-compose:1.0.0-alpha09\"\r\n  }<\/pre>\n<p>\n  In the <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\/tree\/main\/ComposeSamples\/ListDetail\">ListDetail Compose Sample<\/a>, two route strings, <code>list<\/code> and <code>detail<\/code>, are created inside <a href=\"https:\/\/developer.android.com\/reference\/androidx\/navigation\/NavHost\">NavHost<\/a> to define the path to two composable, which are the two views showing at the same time when in dual-screen mode.\n<\/p>\n<pre>val navController = rememberNavController()\r\nNavHost(\r\n    navController = navController,\r\n    startDestination = \"list\"\r\n) {\r\n    composable(\"list\") {\r\n        ListViewUnspanned(\r\n            navController = navController\r\n        )\r\n    }\r\n    composable(\"detail\") {\r\n        DetailViewUnspanned(\r\n            modifier = Modifier.fillMaxSize(),\r\n            navController = navController\r\n        )         \r\n    }        \r\n}<\/pre>\n<h2>Two Page<\/h2>\n<p>\n  The <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/design\/two-page\/?WT.mc_id=docs-surfaceduoblog-joyl1216\">Two Page<\/a> pattern provides a book-like paging experience, which is perfect for a reading application. There is no official ViewPager component in Jetpack Compose currently. So, we borrow the <code>Pager<\/code> implementation in the Jetpack Compose official sample <a href=\"https:\/\/github.com\/android\/compose-samples\/tree\/main\/Jetcaster\">JetCaster<\/a>, and modify it to accommodate the dual-screen scenario inside the <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\/tree\/main\/ComposeSamples\/TwoPage\">Two Page Compose Sample<\/a> as shown below. \n<\/p>\n<p>\n  <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated.png\" alt=\"Surface Duo showing a book reading app\" width=\"2274\" height=\"1778\" class=\"alignnone size-full wp-image-1431\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated.png 2274w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated-300x235.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated-1024x801.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated-768x600.png 768w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated-1536x1201.png 1536w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/compose-two-page-updated-2048x1601.png 2048w\" sizes=\"(max-width: 2274px) 100vw, 2274px\" \/><\/a><br\/><em>Figure 4: Two Page Compose code sample<\/em>\n<\/p>\n<p>\n  We introduce a new property <code>isDualMode<\/code> to identify whether the application is in dual-screen mode or single-screen mode. Since we would open the two pages at the same time in dual-screen mode, we can stop scrolling when the paging reaches the second to last page.\n<\/p>\n<pre>var isDualMode: Boolean = false \/\/ support dual-screen mode\r\nsuspend fun snapToOffset(offset: Float) {\r\n    val max = if (currentPage == minPage) 0f else 1f\r\n    val lastPage = if (isDualMode) maxPage - 1 else maxPage\r\n    var min = if (currentPage == lastPage) 0f else -1f\r\n\r\n    _currentPageOffset.snapTo(offset.coerceIn(min, max))\r\n}<\/pre>\n<p>\n  We also need to adjust the layout to fit two screens with the hinge\/seam in between, if available. We can obtain the hinge size from <a href=\"https:\/\/developer.android.com\/reference\/androidx\/window\/FoldingFeature\">FoldingFeature<\/a> inside <a href=\"https:\/\/developer.android.com\/jetpack\/androidx\/releases\/window\">Jetpack Window Manager<\/a>, then pass it as a new parameter <code>pagePadding<\/code> into <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\/blob\/main\/ComposeSamples\/TwoPage\/src\/main\/java\/com\/microsoft\/device\/display\/samples\/twopage\/utils\/ViewPager.kt\">ViewPager<\/a> we create.\n<\/p>\n<pre>var padding = if (currentPage < page &#038;&#038; state.isDualMode) pagePadding else 0\r\nval xItemOffset = ((page + offset - currentPage) * placeable.width + padding).roundToInt()\r\nplaceable.place(\r\n    x = xItemOffset,\r\n    y = yCenterOffset\r\n)<\/pre>\n<h2>Dual View<\/h2>\n<p>\n  The <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/design\/dual-view\/?WT.mc_id=docs-surfaceduoblog-joyl1216\">Dual View<\/a> pattern can be used to compare two versions of the same type of content side-by-side, like two images, lists, or documents. In the <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\/tree\/main\/ComposeSamples\/DualView\">Dual View Compose Sample<\/a>, we layout a list view and a map view together, like what we did in the List-Detail Compose sample above.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1195\" height=\"933\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-3.png\" class=\"wp-image-1419\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-3.png 1195w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-3-300x234.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-3-1024x799.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-3-768x600.png 768w\" sizes=\"(max-width: 1195px) 100vw, 1195px\" \/>\n<br\/><em>Figure 5: Dual View Compose code sample<\/em>\n<\/p>\n<p>\n  The zooming and dragging is also provided for the map view here, the same as the Extended Canvas Compose sample. But we also need to make sure that the map view doesn\u2019t cross the hinge from the right screen to the left screen and cover the list view when zooming or panning. To achieve this, the map view is put in a <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/package-summary#box\">Box<\/a> with <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/draw\/package-summary#cliptobounds\">clipToBounds<\/a> modifier.\n<\/p>\n<pre>Box(modifier = modifier.then(Modifier.clipToBounds())) {\r\n    scalableImageView(imageId = selectedMapId, isWide = isWide)\r\n}<\/pre>\n<p>\n  For the single screen mode of Dual View, a List\/Map button in the top bar is created to switch between these two types of content with <a href=\"https:\/\/developer.android.com\/reference\/androidx\/navigation\/NavController\">NavController<\/a>.\n<\/p>\n<pre>TopAppBar(\r\n    title = {\r\n        Text(text = stringResource(R.string.app_name)\r\n    },\r\n    actions = {\r\n        IconButton(\r\n            onClick = {\r\n                navController.popBackStack()\r\n            }\r\n        ) {\r\n            Icon(painter = painterResource(id = R.drawable.ic_list))    \r\n        }\r\n    }\r\n)<\/pre>\n<h2>Companion Pane<\/h2>\n<p>\n  The <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/design\/companion-pane\/?WT.mc_id=docs-surfaceduoblog-joyl1216\">Companion Pane<\/a> pattern provides primary\/secondary pane when the app is spanned to dual-screen. Media editing or game controls applications can benefit from this pattern. In the <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\/tree\/main\/ComposeSamples\/CompanionPane\">Companion Pane Compose Sample<\/a>, there are four different containers built to hold four different layouts based on the device orientation and state:<\/p>\n<ul>\n<li>single portrait mode<\/li>\n<li>single landscape mode<\/li>\n<li>dual portrait mode<\/li>\n<li>dual landscape mode<\/li>\n<\/ul>\n<p>For <em>dual portrait mode (left\/right)<\/em>, the <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/RowScope#weight\">weight<\/a> modifier inside <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/RowScope\">RowScope<\/a> is used in the same way as in the List-Detail Compose sample. <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/ColumnScope\">ColumnScope<\/a> also provides a <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/foundation\/layout\/ColumnScope#weight\">weight<\/a> modifier, which can be used for <em>dual landscape mode (top\/bottom)<\/em>.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1069\" height=\"834\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-4.png\" class=\"wp-image-1420\" alt=\"Surface Duo showing an image editing app on both screens, positioned horizontally\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-4.png 1069w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-4-300x234.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-4-1024x799.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-4-768x599.png 768w\" sizes=\"(max-width: 1069px) 100vw, 1069px\" \/><br\/><em>Figure 6: Companion Pane Compose code sample-dual portrait mode<\/em>\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"632\" height=\"813\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-5.png\" class=\"wp-image-1421\" alt=\"Surface Duo showing an image editing app on both screens, positioned vertically\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-5.png 632w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/03\/word-image-5-233x300.png 233w\" sizes=\"(max-width: 632px) 100vw, 632px\" \/><br\/><em>Figure 7: Companion Pane Compose code sample-dual landscape mode<\/em><\/p>\n<p>\n  As an image editing sample application, <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/material\/package-summary#slider\">Slider<\/a> is a very common component. In Jetpack Compose, with <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/runtime\/MutableState\">MutableState<\/a>, it is very easy to detect and reflect the value of a selection change.\n<\/p>\n<pre>fun SliderControl(modifier: Modifier) {\r\n    var sliderPosition by remember { mutableStateOf(0f) }\r\n    Slider(\r\n        value = sliderPosition,\r\n        onValueChange = { sliderPosition = it },\r\n        valueRange = 0f..100f,\r\n        colors = SliderDefaults.colors(\r\n            thumbColor = Color.White,\r\n            activeTrackColor = Color.White,\r\n            inactiveTrackColor = Gray\r\n        ),\r\n    modifier = modifier\r\n    )\r\n}<\/pre>\n<h2>Resources and feedback<\/h2>\n<p>\n  The source code here is <a href=\"https:\/\/github.com\/microsoft\/surface-duo-compose-samples\">available on GitHub<\/a>; we\u2019d love to hear your feedback or contributions to the project. You can also find them summarized in the <a href=\"https:\/\/docs.microsoft.com\/samples\/browse\/?expanded=surface&#038;products=surface-duo&#038;WT.mc_id=docs-surfaceduoblog-joyl1216\">Microsoft Samples Browser<\/a>.\n<\/p>\n<p>\n  Check out the <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/android\/sample-code\/launch-to-second-screen\/?WT.mc_id=docs-surfaceduoblog-joyl1216\">Surface Duo developer documentation<\/a> and <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/dual-screen-react-web\/\">past blog posts<\/a> for links and details on all our samples.\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  You can also <a href=\"https:\/\/www.twitch.tv\/surfaceduodev\">chat with us on Twitch<\/a> on Friday, March 26th at 11am PDT, where you can ask questions about Jetpack Compose or any other aspect of developing dual-screen apps for the Surface Duo!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hi Android developers, We talked about using Jetpack Compose, the new Android UI toolkit for Microsoft Surface Duo development in a previous blog post. Now, Jetpack Compose is in beta. Combined with the use of Jetpack Window Manager, Jetpack Compose is more flexible for developing apps for dual-screen devices and even easier to extend to [&hellip;]<\/p>\n","protected":false},"author":30456,"featured_media":1425,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[692,473,46],"class_list":["post-1415","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-surface-duo-sdk","tag-jetpack-compose","tag-kotlin","tag-surface-duo"],"acf":[],"blog_post_summary":"<p>Hi Android developers, We talked about using Jetpack Compose, the new Android UI toolkit for Microsoft Surface Duo development in a previous blog post. Now, Jetpack Compose is in beta. Combined with the use of Jetpack Window Manager, Jetpack Compose is more flexible for developing apps for dual-screen devices and even easier to extend to [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1415","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=1415"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1415\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media\/1425"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media?parent=1415"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/categories?post=1415"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/tags?post=1415"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}