Adding Microsoft Surface Duo support to the Flokk application

Avatar

John

Image FlokkHero

Hello Flutter developers!

During the final week of July, Microsoft held its annual hackathon. The event has had different names over the years, but the idea has remained the same; people from across the company come together to work on projects they are interested in. There are many different types of projects, some are simply a new feature for an existing product, some are more aspirational, and some are just for fun. This year, one project in particular caught my eye. James Clarke (@clarkezone) was leading a project to bring the Flokk contact application to Windows and Surface Duo. James asked me to join his team to help with the Surface Duo support and away we went! This blog post outlines the steps we took to adapt an existing Flutter desktop application to work on Surface Duo in just three short days.

The Flokk application is a sample desktop Flutter application from the team at gskinner, a design agency specializing in Flutter. Ok, but what does it do?

“Flokk is a Google Contacts Manager, targeting Desktop, that allows you to integrate the Twitter and GitHub activity of your friends and contacts.”

What we want to show in this post are the steps it takes to add Surface Duo support to an existing application. Step 1 and 2 are things unique to our project as we did not build the original application. If you built the application, you probably already know all the stuff we got from the first two steps and will begin at step 3.

Step 1 – Review the Available Information

The team at gskinner has put together some amazing blog posts to talk about what they did and how they did it. This information proved to be very valuable when we began digging into the codebase. They split the posts up into different topics so you can read any or all of them depending on the information you are looking for. Here are the two main blog posts they have published about the application.

They have also published some ancillary posts about topics that relate to how they did things in the applications.

If you’d like to learn more about how they built the application and the tricks they employed to accomplish certain things, I recommend giving those a read. As with any project, if there is documentation you can review before you go in to make changes or additions, the docs are the best place to start.

Reviewing their documentation, we were able to get a good understanding of how they built the application. Specifically, it helped to know how they went about laying out the different views within the application. In a typical application you may have each view be its own widget. Then you navigate from view to view. Had we just assumed that this was how the app was working, we would have been banging our heads for a while trying to figure out just how they were moving between views. By reading the documentation first, we knew that they were not actually navigating between views, but using a more subtle solution of overlaying the views on top of each other and then showing/hiding as appropriate based on user interaction.

Step 2 – Review Comps and Source Code

The gskinner team had put together some comps about what the application might look like on a dual-screen device like Surface Duo and they were kind enough to share them with us when we told them what we were up to. These comps gave us a good starting point of what the final application should resemble when running spanned across two screens. With these comps in mind, and the knowledge we gathered from the documentation, we turned to the code.

In the source we began with the structure, reviewing the folder and file structure to get familiar with how they grouped the code. From the blog posts, we knew that they had used an MVC like architecture, so we expected to find a common folder structure for MVC applications. What we found was just that. They had a Views folder with each of the views having a subfolder under that where they grouped the views and their related widgets. There was a Models folder, a Services folder, and a Commands folder. Each of those held the respective files one would expect to find there.

Image: Flokk project structure

 

Because this was a desktop application, it also had folders for each of the platforms that were supported at the time: Linux, macOS, and Web. There was now Windows to consider, and by now, you may also be asking “What about iOS and Android”?

Remember, this was a sample desktop application, so it didn’t have phone support and the Windows support was also not there yet; Windows was still not available in Alpha, like it is today as I write this post. Part of our hack was to also ensure it ran well on Windows so we could report back any issues. We’re happy to say that our testing showed that outside of one piece that was yet another side project of the hack, it all worked great on Windows, on the desktop build, at the time we tested.

Ok, but that doesn’t explain how we got the required Android and iOS support into the app in order to get it running on Surface Duo. After reviewing the folder structure and the files, we dove in and the first thing we did was add the support for Android and iOS. This was simple as all we had to do was run the Flutter create command to get the Flutter SDK to create the needed folders and files for each.

PS C:\SourceCode\GitHub\flokk\flokk_src> flutter create .

This provided the needed infrastructure; next we needed to test it out and see if it worked. To do this, we spent some time using the application on Android and iOS devices and emulators. Turns out, the application worked almost completely as is on those devices. There were a few issues that we fixed up, like getting it to launch URLs correctly on the various platforms. We’re not sure why the gskinner team did not release it for mobile devices originally, but our speculation is that the app is not as usable in landscape mode and this is likely why.

With the app setup and running on Android devices, it was time to begin adding the support for Surface Duo.

Step 3 – Adding Surface Duo Support

We have done a couple of posts where we talk about how to add support for the Surface Duo APIs into a Flutter application. If you’d like to read up on the steps, here are the links to those posts.

After those posts were published, we saw a few folks in the Flutter community take the information and build out packages for Flutter to make it even easier for developers to get going on Surface Duo. For this project, we thought we’d use one of the packages that exposed some of the values as streams to make them easier to consume in the application. To use the package, all we needed to do was add the Multiple Screens package to the pubspec.yaml file. One of the things we need to know is how much of the screen the hinge is covering up. To get this information, we did have to implement some of the code from our articles to expose that value to us. Once this was done, we were ready to move on to working on each of the views.

Image: Pub.dev page for multiple_screens package

 

Step 4 – Working on the Views

First, why “views” and not widgets? The Flokk application doesn’t follow the normal Flutter app where everything is a widget and each “screen” you see on the device is a single widget made up of a bunch of widgets. What the app does is simply move widgets around on the screen so they are visible or off screen or covered up. So in this case, using “view” makes more sense.

There are a number of ways you can use the package to access the APIs and each has pros and cons. While looking over the code and trying to determine where we would need to insert logic to handle when the application was spanned and laying out the UI, we decided that it would be easiest and most reliable if we inserted the code needed into each of the widgets where we needed the info rather than try and place it higher up in the app stack and refer back to it. Keep in mind that we only had three days to hack together our solution, so perhaps a more thorough review would point to a simpler or better solution.

The first view we began to work on was the welcome screen. We quickly noticed when testing the application that if you spanned the app while on the welcome screen that the UI simply didn’t look like it should, as can be observed in the below screenshot.

Image: Flokk running spanned on Surface Duo before changes, ex. 1

 

The steps we need to take are similar on all the views:

  1. Insert the package into the imports
  2. Find where and how the different pieces of the UI are laid out
  3. Determine how to split up the display onto the two screens when the app is spanned
  4. Write the code to detect when spanned and make the appropriate layout changes
import 'package:multiple_screens/multiple_screens.dart';

For the welcome_page.dart we added the import for the multisple_screens package, then began looking at the code. The first thing we needed was some variables to hold out information.

// Surface Duo platform support
final duoPlatform = const MethodChannel('duosdk.microsoft.dev');
bool isDuo = false;
bool isDuoSpanned = false;
double hingeSize = 0.0;

With our variables in place, we needed to initialize them in initState().

MultipleScreensMethods.isAppSpannedStream().listen(
  (data) => setState(() => isDuoSpanned = data),
);

checkForDuo();

Because we need to execute some async calls, we created a checkForDuo method.

void checkForDuo() async {
  try {
    isDuo = await MultipleScreensMethods.isMultipleScreensDevice;
    hingeSize = await duoPlatform.invokeMethod('gethingeSize');
  } catch (_) {
    // if we fail it is likely because we aren't on a Surface Duo
  }
}

With this in place, we now know if we are running on a Surface Duo, what space the Hinge is taking up in the display, and we are also getting any updates to our spanned state. We can now move into the rest of the code to modify the view to display properly when spanned. The first thing we found was that the view is using the width of the application to determine if it should show the view in one column or two columns. You can see this if you make the window narrow and then expand it to be wider when running on desktop. To ensure that we use a two-column display when we run on a Surface Duo, we modified the following code. We also modified the lines after to create the proper values that are used in other places to layout the content.

state.twoColumnMode = context.widthPx > columnBreakPt || state.isDuoSpanned;

Just below that line we see that the code is determining the width of the content to use when laying out content later on. We needed to modify this to support the spanned state and ensure we had a content area that matched the width of our single screen.

// Calculate how wide we want the panel, add some extra width as it grows
double contentWidth = state.isDuoSpanned
  ? MediaQuery.of(context).size.width / 2 - state.hingeSize
  : state.twoColumnMode
    ? 300
    : double.infinity;

if (state.twoColumnMode && !state.isDuoSpanned) {
  // For every 100px > the PageBreak add some panel width. Cap at some max width.
  double maxWidth = 700;
  contentWidth += min(maxWidth, context.widthPx * .15);
}

Image: Flokk running spanned on Surface Duo before changes, ex. 2

 

Next, we began looking at how the content layout was being done. A really useful way to try and figure out what needs to be changed and where it lives in the code is to use the Flutter debugging tools. Using these, we found the right place in the Scaffold that we needed to change to make the view display like we wanted when spanned.

…

Container(
  alignment: Alignment.center,
  child: Padding(
    padding: state.isDuoSpanned ? EdgeInsets.only(right: 20.0) : EdgeInsets.all(0.0),
    child: AnimatedBirdSplashWidget(
      showText: state.isLoading,
      showSpannedView: state.isDuoSpanned,
    ),
  ),
).opacity(1.0).padding(right: (state.showContent && state.twoColumnMode ? contentWidth : 0), animate: true).animate(
  skipBirdTransition ? 0.seconds : Durations.slow,
  Curves.easeOut,
)

…

Image animatedscreens

Animated gif: Finished Flokk views running spanned across both screens on the Surface Duo

 

We continued to go through these steps for each of the views within the application. The welcome view has a couple of different views within it so we worked through those then moved on to the main scaffold in the dashboard and the contacts view.

After doing one view, the others went pretty quick as it was simply repeating the process in the view where content was being laid out and using the debugging tools to help determine what was being laid out, where, and how. Once we had that, modifying the code was pretty straightforward, though sometimes taking a little trial and error.

Image: Flutter dev tools running in Visual Studio Code

 

Step 5 – Testing

We did a lot of testing as we worked on the changes to the application. Once we had things in a state we were happy with, and we were getting close to wrapping up our three days of work, we turned to testing the application with all the changes on the original devices it was made to work on, desktops. We spent time ensuring that all our changes to support Surface Duo didn’t break the application on macOS, Linux, and the Web.

It was reassuring to find that none of the changes we made caused any issues when running on the desktop. We kept our changes pretty focused on only being applied when we knew we were running on a Surface Duo and when we were spanned; otherwise, the existing code worked almost 100% as it was written when running on a single screen.

What’s Next?

Next up is to finish the rest of the views, add in support for landscape and double-landscape postures, and then submit our Pull Request. We are hoping to wrap this up soon so everyone can try out the application on their devices and review the changes we made to make it all work. We hope this inspires Flutter developers to work on adding support for dual-screen devices into their applications.

Resources and feedback

The Flokk contact application is open sourced and can be found on GitHub at https://github.com/gskinnerTeam/flokk.

We would love to hear from you about your experiences using the Surface Duo SDK, emulator, and your thoughts on how you can utilize these in your Flutter applications.

Please reach out using our feedback forum or message me on Twitter or GitHub.

0 comments

Leave a comment