{"id":452,"date":"2020-06-18T13:11:04","date_gmt":"2020-06-18T20:11:04","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/surface-duo\/?p=452"},"modified":"2020-09-11T18:11:59","modified_gmt":"2020-09-12T01:11:59","slug":"lighting-up-a-flutter-application-on-microsoft-surface-duo","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/surface-duo\/lighting-up-a-flutter-application-on-microsoft-surface-duo\/","title":{"rendered":"Lighting-Up a Flutter application on Microsoft Surface Duo"},"content":{"rendered":"<p>Hello Flutter developers!<\/p>\n<p>In our last post, <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/get-started-with-flutter-on-surface-duo\/\">Get Started with Flutter on Surface Duo<\/a>, we walked you through how to add the Surface Duo SDK to your Flutter project and how to use the SDK to obtain information from the SDK library. We can now tell if the device we are running on is a dual-screen device, if your application is running on one screen or both, and even what angle the hinge is currently at. A few of you have even gone so far as to begin creating packages for Flutter to help developers get started even quicker. You are encouraged to check out these packages, perhaps you might even like to contribute to them: <a href=\"https:\/\/pub.dev\/packages\/multiple_screens\">multiple_screens<\/a>, and <a href=\"https:\/\/github.com\/MisterJimson\/surface-duo\">surface_duo<\/a>.<\/p>\n<p>In this post, we will pick up where we left off in the last post and walk you through adding one more bit of code to determine the area between the two screens, or Display Mask (hinge), is taking up. Once we have that, we\u2019ll dive in to how you can use the APIs from our previous post, and this new one, in your applications to make them light-up on dual-screen devices. If you\u2019d like to follow along, you can clone the starter_sample application from the <a href=\"https:\/\/github.com\/microsoft\/surface-duo-sdk-samples-flutter\">Flutter samples GitHub repository<\/a>.<\/p>\n<h2>Adding the Display Mask Code<\/h2>\n<p>Our first task is to add code to determine just how much width (spanned, portrait mode) or height (spanned, landscape mode) the hinge is taking up. If you aren\u2019t familiar with all the configurations available on a dual-screen device, we encourage you to <a href=\"https:\/\/docs.microsoft.com\/en-us\/dual-screen\/introduction\">read the documentation<\/a> where you can get all the info. Basically, when your application is spanned across the two screens, there is a portion of the display where the hinge is, that is non-viewable. It is up to you to determine how to handle this area within your application and we do provide some good guidance in our documentation.<\/p>\n<p>Let\u2019s add the following code into our MainActivity.kt file:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"prettyprint\"> fun getHingeSize(): Int {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ The size will always be the same,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ it will either be the height (Double Landscape)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ or the width (Double portrait)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0val displayMask = DisplayMask.fromResourcesRectApproximation(activity)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0val boundings = displayMask.boundingRects\r\n\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if (boundings.isEmpty()) return 0\r\n\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0val first = boundings[0]\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0val density: Float = activity!!.resources.displayMetrics.density\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0val height = ((first.right \/ density) - (first.left \/ density)).toInt()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0val width = ((first.bottom \/ density) - (first.top \/ density)).toInt()\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if (width &lt; height)\r\n        {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return width\r\n        }else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return height\r\n        }\r\n\u00a0\u00a0\u00a0\u00a0}<\/pre>\n<p>This code will provide us with the height, or width of the hinge. We first get the Display Mask information from the API. If there is no Display Mask, then we are not spanned, and we return zero. Next, we use the information to calculate the height and width of the Display Mask. Note that we could simply return the entire object with the top, bottom, left, and right values and allow the Flutter program to determine how to use them, but in this case we are simply going to return the smaller of the height and width. Why the smaller? Well, if you picture the hinge, you can see that no matter how the device is oriented, the hinge area between the two sides will always be the smaller of height vs. width values.<\/p>\n<p><img decoding=\"async\" width=\"1171\" height=\"600\" class=\"wp-image-453\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-10.png\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-10.png 1171w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-10-300x154.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-10-1024x525.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-10-768x394.png 768w\" sizes=\"(max-width: 1171px) 100vw, 1171px\" \/><\/p>\n<p><em>Figure 1: Application spanned in both double-portrait and double-landscape mode<\/em><\/p>\n<p>Now that we have that code in place, let\u2019s use it in the <strong>MethodChannel<\/strong> handler. Your new handler will look something like this.<\/p>\n<pre class=\"prettyprint\">    \/\/ Surface Duo: Here is were we define the Method Channel and how we handle the requests\r\n    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, \r\n                  CHANNEL).setMethodCallHandler { \r\n    call, result -&gt;\r\n        if (!isDualScreenDevice()) {\r\n            result.success(false)\r\n        } else {\r\n            try {\r\n                when (call.method) {\r\n                    \"isDualScreenDevice\" -&gt; {\r\n                        if (isDualScreenDevice()) {\r\n                            result.success(true)\r\n                        } else {\r\n                            result.success(false)\r\n                        }\r\n                    }\r\n                    \"isAppSpanned\" -&gt; {\r\n                        if (isAppSpanned()) {\r\n                            result.success(true)\r\n                        } else {\r\n                            result.success(false)\r\n                        }\r\n                    }\r\n                    \"getHingeAngle\" -&gt; {\r\n                        if (!mSensorsSetup) {\r\n                            setupSensors()\r\n                        }\r\n                        result.success(mCurrentHingeAngle)\r\n                    } \r\n                    \"getHingeSize\" -&gt; {\r\n                    result.success(getHingeSize())\r\n                    } \r\n                    else -&gt; {\r\n                        result.notImplemented()\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }<\/pre>\n<p>Great, we now can get the hinge size; let\u2019s use all this info in our sample application.<\/p>\n<h2>Lighting up the application<\/h2>\n<p>We are using the stock Flutter template you get when creating a new Flutter app. If you go ahead and span the application in the emulator, you will quickly notice that the view is not ideal for the user.<\/p>\n<p><img decoding=\"async\" width=\"1171\" height=\"522\" class=\"wp-image-454\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-11.png\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-11.png 1171w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-11-300x134.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-11-1024x456.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-11-768x342.png 768w\" sizes=\"(max-width: 1171px) 100vw, 1171px\" \/><\/p>\n<p><em>Figure 2: Application on single screen and spanned<\/em><\/p>\n<p>The text is hidden underneath the hinge area and the number showing how many times we\u2019ve clicked the button is also obscured. Let\u2019s see how we can utilize the information we now have available to make this more usable. We\u2019ll be doing minimal work to make this one-page application dual-screen aware, but all the techniques used would apply to any screen in your application. In a future post, we\u2019ll dig deeper into how we can make this even easier to migrate an application with a much more complex architecture.<\/p>\n<p>First, we\u2019ll need to determine if we are running on a dual-screen device, and for this verification, we\u2019ll use the <strong>isDualScreenDevice<\/strong> <strong>MethodChannel<\/strong>. We\u2019ll override the <strong>InitiState<\/strong> method and set a class level variable since this only needs to be set once as this value won\u2019t change. Next, pull the content from the <strong>body <\/strong>of our Scaffold into a new method called <strong>_buildBody<\/strong>.<\/p>\n<p>Our new Scaffold will look like this:<\/p>\n<pre class=\"prettyprint\">  return Scaffold(\r\n    appBar: AppBar(\r\n      title: Text(widget.title),\r\n    ),\r\n    body: _buildBody(),\r\n    floatingActionButton: FloatingActionButton(\r\n      onPressed: _incrementCounter,\r\n      tooltip: 'Increment',\r\n      child: Icon(Icons.add),\r\n    ),\r\n  );\r\n<\/pre>\n<p>And our new _buildBody method looks like this:<\/p>\n<pre class=\"prettyprint\">  Widget _buildBody() {\r\n    return Center(\r\n      child: Column(\r\n        mainAxisAlignment: MainAxisAlignment.center,\r\n        children: &lt;Widget&gt;[\r\n          Text(\r\n            'You have pushed the button this many times:',\r\n          ),\r\n          Text(\r\n            '$_counter',\r\n            style: Theme.of(context).textTheme.headline4,\r\n          ),\r\n        ],\r\n      ),\r\n    );\r\n  }\r\n<\/pre>\n<p>If you followed along with our first post, you may recall that we had an <strong>_updateDualScreenInfo<\/strong> method. We have moved the local variables out of that method and turned them into class level variable on <strong>the_MyHomePageState<\/strong> class so we can reference them in other places. You can check the final project if you aren\u2019t sure what changes to make.<\/p>\n<p>In order to ensure that our variables that tell us if the application is spanned and what the display mask size is or updated to when there is a change <em>before<\/em> we build the body, we need to use a <a href=\"https:\/\/api.flutter.dev\/flutter\/widgets\/FutureBuilder-class.html\">FutureBuilder<\/a>. We\u2019ll wrap the body of our Scaffold in a FutureBuilder, which will end up looking like this:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"prettyprint\">  return Scaffold(\r\n  ...\r\n    body: FutureBuilder&lt;void&gt;(\r\n      future: _updateDualScreenInfo(),\r\n      builder: (BuildContext context, AsyncSnapshot&lt;void&gt; snapshot) {\r\n        return _createPage();\r\n      },\r\n    ),\r\n  ...\r\n<\/pre>\n<p>What is happening here? Well, we\u2019ve set the <strong>future<\/strong> to call our method that updates the dual screen info variables. This is inside our build method so each time we try to rebuild the widget this will call our update method. Since we\u2019re calling from our FutureBuilder, it will happen <em>before<\/em> our builder inside the FutureBuilder runs. With this in place, we can be sure that we\u2019ll always have the most current info when we run our builder. Let\u2019s create a new method, <strong>_createPage<\/strong> that simply returns<strong> _buildBody()<\/strong> for now. We\u2019ll modify it next.<\/p>\n<pre class=\"prettyprint\">  Widget _createPage() {    \r\n    return _buildBody();\r\n  }\r\n<\/pre>\n<p>Next, let\u2019s focus on the <strong>_createPage<\/strong> method. First, we wrap our return code in an if\/else block. If we are not on a dual screen device, or we are but we are not spanned, then we will return the default Center and Column from the template. If we are on a dual-screen device and we are spanned, then we\u2019ll simply return a blank Container for now.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"prettyprint\">  if(!_isDualScreenDevice || (_isDualScreenDevice &amp;&amp; !_isAppSpanned)) {      \r\n    \/\/ We are not on a dual-screen device or \r\n    \/\/ we are but we are not spanned\r\n    return _buildBody();\r\n  } else {\r\n    \/\/ We are on a dual-screen device and we are spanned\r\n    return Container();\r\n  }\r\n<\/pre>\n<p>Ok, so now we have a working app if we are on a single-screen device or a dual-screen device and not spanned, but if we are spanned we will get a blank screen. We need to fix that now.<\/p>\n<p>To fix this, let\u2019s change our else block in the _createPage method. We\u2019ll do something simple, like display the Flutter logo on the left and push the content over to the right side (top and bottom if we\u2019re rotated).<\/p>\n<p>Because we can be in either portrait or landscape mode and spanned, we need to check that first, so we\u2019ll add a new if\/else in place of the <strong>return Container()<\/strong> line:<\/p>\n<pre class=\"prettyprint\">  \/\/ We are on a dual-screen device and we are spanned\r\n  if (MediaQuery.of(context).orientation == Orientation.portrait) {\r\n    \/\/ Portrait is what we get when we have rotated the device\r\n    \/\/ and have two \"landscape\" screens on top of each other,\r\n    \/\/ so together they are \"portrait\"\r\n  } else {\r\n  }\r\n<\/pre>\n<p>Now let\u2019s focus on the else block. This is where we will end up when the application is spanned on the device in its normal orientation, like in this image:<\/p>\n<p><img decoding=\"async\" class=\"wp-image-455\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-12.png\" width=\"441\" height=\"382\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-12.png 602w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/word-image-12-300x260.png 300w\" sizes=\"(max-width: 441px) 100vw, 441px\" \/><\/p>\n<p><em>Figure 3: Image of application spanned in landscape orientation<\/em><\/p>\n<p>Here we want a Row with children. We\u2019ll use our <strong>_hingeSize<\/strong> information to create an \u201cempty\u201d space in the middle to ensure we don\u2019t lose any content. Our code in the else block should now look like this:<\/p>\n<pre class=\"prettyprint\">  } else {\r\n    return Row(\r\n      children: [\r\n        Flexible(\r\n          flex:1,\r\n          child: Center(child: FlutterLogo(size: 200.0)),\r\n        ),\r\n        SizedBox(width: _hingeSize.toDouble()),\r\n        Flexible(\r\n          flex:1,\r\n          child: _buildBody(),\r\n        ),\r\n      ],\r\n    );\r\n  }\r\n<\/pre>\n<p>The beauty in this is that it is very simple. We have a SizedBox that is the width of the hinge and on either size, we have one child that takes up the space on that size. We center a <a href=\"https:\/\/api.flutter.dev\/flutter\/material\/FlutterLogo-class.html\">FlutterLogo <\/a>on one side and then use the original code now in our <strong>_buildBody<\/strong> method on the other side. For the if block, all we have to do is copy what we have in the else, then change the Row to be a Column and the SizedBox from a width to a height based box.<\/p>\n<pre class=\"prettyprint\">  return Column(\r\n    children: [\r\n      Flexible(\r\n        flex: 1,\r\n        child: Center(child: FlutterLogo(size: 200.0)),\r\n      ),\r\n      SizedBox(height: _hingeSize.toDouble()),\r\n      Flexible(\r\n        flex:1,\r\n        child: _buildBody(),\r\n      ),\r\n    ],\r\n  );\r\n<\/pre>\n<p>We now have a final app that will adjust the output based on if we are on a Dual-Screen device and if the app is spanned across screens or not. There is so much more we can do and will have to do when we need to support navigation from page to page. We need to consider what content should appear where and whether it should it be a new page on the right if the app is spanned. Should it be a single page that lays out differently when spanned vs. not spanned? What happens with the content already on the screen? Lucky for us, there is a lot of guidance already available on this to help us out. What we\u2019ll need to do is implement something that helps us add the functionality in an easy way.<\/p>\n<p><img decoding=\"async\" class=\"alignnone wp-image-456\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/Post-2-Hero-Image-300x260.png\" alt=\"Flutter Post 2 Hero Image\" width=\"441\" height=\"382\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/Post-2-Hero-Image-300x260.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/Post-2-Hero-Image-768x665.png 768w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2020\/06\/Post-2-Hero-Image.png 798w\" sizes=\"(max-width: 441px) 100vw, 441px\" \/><\/p>\n<p><em>Figure 4: Screenshot of the finished application spanned<\/em><\/p>\n<p>Already, we are seeing people beginning to think about this. Check out the beginning of this post and the reference to the packages that have begun popping up. There is room to improve and in our next post, we\u2019ll dig into a way this can be accomplished that has the least amount of code changes on an existing Flutter application.<\/p>\n<h2>Resources and feedback<\/h2>\n<p>UPDATE: You can find the final project, <strong>lightup_sample<\/strong>, in our <a href=\"https:\/\/github.com\/microsoft\/surface-duo-sdk-samples-flutter\">GitHub repository<\/a><\/p>\n<p>We would love to hear from you about your experiences using the Surface Duo SDK, emulator, as well as your thoughts on how you can utilize these in your Android apps.<\/p>\n<p>Please reach out using our <a href=\"http:\/\/aka.ms\/SurfaceDuoSDK-Feedback\">feedback forum<\/a> or message me on <a href=\"https:\/\/twitter.com\/johnwiese\">Twitter<\/a> or <a href=\"https:\/\/github.com\/jwiese-ms\">GitHub<\/a>.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello Flutter developers! In our last post, Get Started with Flutter on Surface Duo, we walked you through how to add the Surface Duo SDK to your Flutter project and how to use the SDK to obtain information from the SDK library. We can now tell if the device we are running on is a [&hellip;]<\/p>\n","protected":false},"author":28023,"featured_media":456,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[693],"class_list":["post-452","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-surface-duo-sdk","tag-flutter"],"acf":[],"blog_post_summary":"<p>Hello Flutter developers! In our last post, Get Started with Flutter on Surface Duo, we walked you through how to add the Surface Duo SDK to your Flutter project and how to use the SDK to obtain information from the SDK library. We can now tell if the device we are running on is a [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/452","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\/28023"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/comments?post=452"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/452\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media\/456"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media?parent=452"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/categories?post=452"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/tags?post=452"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}