Surface Duo

A little over two years ago I began working with Flutter. I have programmed in many languages over the years: C++, Visual Basic, C#, Xamarin and a host of others for a small project or two. Flutter really caught my eye though, and I really enjoyed how quickly you could develop something with it and iterate so fast. I couldn’t find any projects to use Flutter on so all my work was for side projects of mine and to simply stay abreast of what was going on in the community. Many of you reading this may know me already from my time spent in the gitter forum for Flutter responding to questions, or from the Hump Day QnA, or other community events.

Finally, on Oct. 2, 2019 Microsoft announced the Surface Duo and Surface Neo devices. While slated for release Holiday 2020, this was my chance to put my Flutter skills to work on an actual project or two. I reached out internally and connected with a small team working on demo apps for the Surface Duo, and was asked to produce some sample apps for the device. That brings us to where we are today.

Welcome to my series on bringing Flutter applications to the Surface Duo. You may be asking “what about the Surface Neo?”. We’ll get there, but that will be down the road after we’ve covered moving to the Surface Duo first. Because the Duo OS is Android, any application you build for Android today with Flutter will simply “just work”. That’s great, but not ideal, as it will only run on one screen and the Duo has two of them. Users will definitely want the applications they use to take as much advantage of the dual-screen device as possible to enable the most productivity. So, how do you as a Flutter developer do that, how do you ensure that your application takes advantage of the dual-screen Duo without making too many changes to your application?

Here is our end goal, a single codebase that runs on iOS, Android (single-Screen), Android (dual-screen, like the Duo), and eventually Windows, Web and anything else Flutter supports :) You can see the prototype we’ll build in this video.

Single codebase running on all devices

STEP 1 — Download the SDK

To get started you will first need to download the latest Surface Duo SDK from https://docs.microsoft.com/en-us/dual-screen/android/get-duo-sdk. The direct link to the download is this one.

After installing the SDK you will have a Duo emulator you can use to test your apps on. You’ll also have all the necessary bits to get going.

STEP 2 — Integrating the SDK into Flutter

Begin by creating a Flutter application and make sure to leave kotlin support enabled (default). The sample app that I’m building is the Contoso Movies application. You can see what the “finished” application will look like in the video above. I say “finished” because there are some things in that video that aren’t optimized for the dual-screen experience yet. This way we have something to work on down the road :)

Ok, now that you have the typical counter application created, let’s add the SDK integration. First, we need to add a file to our Flutter app, inside the Android section. Start by creating a folder below /android/app, I called mine “libs”.

Create a folder for the SDK library

Now that we have a folder, we need to copy the SDK library to it. On Windows this will typically be found at:

C:\Users\<username>\SurfaceDuoEmulator\sdk

In the directory you will find a JAR file, copy this file into the folder you created in your application. The last step is to add a dependency on this file to your app level build.gradle. Open the build.gradle found in the android/app folder. Scroll down to the “dependencies” section and add the following line inside it:

compileOnlyfiles(‘libs/com.microsoft.device.display.displaymask.jar’)

Your dependencies section should now look like this if you started will a new Flutter application.

Modified build.gradle

STEP 3 — Add the native code

Now that we have the SDK in the app we need to add the relative native code that we can then call from Flutter to check things like, “is this a dual-screen device” and “is my app spanned across both screens” among other things. We’ll start with these two for now. Open the MainActivity.kt file found in /android/app/src/main/kotlin.

We’ll first need a couple of imports, one to be able to create a Channel for Flutter to communicate over and a second to import the library. Add these two lines to the imports section of your app.

import io.flutter.plugin.common.MethodChannel

import com.microsoft.device.display.DisplayMask

Next we need to create the channel we’ll use to communicate and ensure that the Native side and Flutter side are in sync. Add the following declaration inside the MainActivity.

private val CHANNEL = “duosdk.microsoft.dev”

To make things readable we’ll break our two pieces of code into two functions that we can call from within the Channel’s methodCallHandler. Add the following two functions to MainActivity.

fun isDualScreenDevice(): Boolean {

val feature = "com.microsoft.device.display.displaymask"

val pm = this.getPackageManager() if (pm.hasSystemFeature(feature)) {

return true

} else {

return false

}

} fun isAppSpanned(): Boolean {

var displayMask = DisplayMask.fromResourcesRectApproximation(this)

var boundings = displayMask.getBoundingRects()

var first = boundings.get(0)

var rootView = this.getWindow().getDecorView().getRootView()

var drawingRect = android.graphics.Rect()

rootView.getDrawingRect(drawingRect) if (first.intersect(drawingRect)) {

return true

} else {

return false

}

}

The last piece we need to add to the MainActivity is the code to handle the calls to this channel. This code will return a failure if we aren’t on a dual-screen device and will handle our two use cases which I’ve called isDualScreenDevice and isAppSpanned. This code should go right below the GeneratedPluginRegistrant.registerWith(flutterEngine); line.

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->



if(!isDualScreenDevice()) {

result.success(false)

} else {

try {

if (call.method == "isDualScreenDevice") {

if (isDualScreenDevice()) {

result.success(true)

} else {

result.success(false)

}

} else if (call.method == "isAppSpanned") {

if (isAppSpanned()) {

result.success(true)

} else {

result.success(false)

}

} else {

result.notImplemented()

}

} catch(e: Exception){

result.success(false)

}

}

}

STEP 4 — Make the calls from Flutter

Now that we have the native side setup we can make use of that functionality within our Flutter application code. Let’s modify our main.dart file now. Add the following import so we can make the platform specific calls.

import 'package:flutter/services.dart';

Next we need to create the platform channel similar to what we did on the native side in the MainActivity. We’ll define a global constant. Not ideal, but for our sample this is fine.

const platform = const MethodChannel('duosdk.microsoft.dev');

Make sure the string you use here matches what you used in the MainActivity code.

In the MyHomePage stateful widget, lets now add a function that will simply print out if we are on a dual-screen device and is the app is spanned.

void _updateDualScreenInfo() async {

final bool isDual =

await platform.invokeMethod('isDualScreenDevice');

final bool isSpanned =

await platform.invokeMethod('isAppSpanned'); print('isDualScreenDevice : $isDual');

print('isAppSpanned : $isSpanned');

}

And finally, let’s call that new function from our build.

@override

Widget build(BuildContext context) {

_updateDualScreenInfo(); ... // The rest of the build method

}

That’s it, we now have an application that will work on both a dual-screen and single-screen device. If you run the application and watch the debug console in your debugger, you will see our output change when you span the app or if you run on a single screen device vs. a dual-screen device.

To see the output change you will need to click the ‘+’ button on the screen so that the build method is called again otherwise spanning or un-spanning the app doesn’t trigger the build method as no state is changing. This is something we’ll talk about addressing later in the series.

The final source files can be viewed from their respective GIST files if you have any problems.

This gif shows the final project, notice the output showing the dual-screen and spanned state as the ‘+’ button is clicked.

A gif of the final project showing the debug output

Conclusion

This has laid the groundwork we need to move into the next stage of building for the Surface Duo. Next time we’ll look at how we can use this info to decide how to layout our content and take advantage of both single and dual-screen modes.

This was my first blog post in nearly 12 years, so I hope you found it interesting and useful, and that you’ll begin exploring how to add support for your Flutter applications on the Surface Duo. I welcome any comments or suggestions.