Surface DUO: how dual-screen devices work in practice
Surface DUO is the most exciting device I’ve seen in years. Built by Microsoft, it will run Android on a dual-screen setup which allows for apps to run side by side or extended, using both screens.
In this article, I want to take a look at what do we need to build a Master/Detail app for dual-screen devices.
To prepare the post, I’ve rewritten from scratch this Master/Detail sample provided by Microsoft to help me better understand the Surface Duo and to:
- Reduce the number of fragments used in the sample and (hopefully) make it easier to understand.
- Explore how
ViewModel
&LiveData
can be used in dual-screen setups. - Add support for Portrait Dual Mode, which the original sample didn’t have.
Here’s a link to the repository if you rather jump directly into the code:
Mind the hinge
A dual-screen device is more than a tablet, we can’t just rely on the app’s landscape layouts to provide the best experience for our users.
The hinge has a visual and conceptual impact. Having that separation in the middle of the screen forces us to organise our content differently. Our landscape layouts can’t just be a wider portrait with the content aligned in the middle, otherwise, the hinge will cover part of it:
We need to design our layouts having the hinge in mind. We’ll see that there are functions in the Surface Duo SDK to detect if there’s a hinge and where.
There’s also the conceptual implications. Arranging content around the hinge is not enough. Its presence makes a clear separation between both screens and the user will expect the content on them to have different responsibilities. In this case, we’ll explore the Master/Detail concept but there are many more options: Two pages, Companion, Dual view, etc.
Master/Detail: what we need
A Surface Duo device can be in either portrait or landscape and for each one of those rotations our app can be displayed in single mode or dual mode. That’s four options but, as we’ll see, only three different layouts will be needed to cover all of them.
What we need:
ListFragment
to display the list ofItem
DetailsFragment
to display one simpleItem
MainActivity
to hold both fragments (either in Single Mode or Dual Mode)MainViewModel
to hold the view logic and serve as a communication bridge between fragments
Dual Mode is the tricky bit.
In Single Mode, we only need to display one fragment at a time, but in Dual Mode our app needs to display both ListFragment
and DetailsFragment
together.
Here’s when things become interesting. Single Mode only cares about one screen, so for both portrait and landscape we can get away with just one layout if we design it to work well in both orientations. But Dual Mode in portrait needs to arrange things from top to bottom and, in landscape, from left to right.
This means we need three different layouts for MainActivity
:
- Layout 1: Single Mode. This is the one we’re used to, with space for one single fragment and used both in portrait and landscape
- Layout 2: Dual Mode Portrait: With space for two fragments and a gap for the hinge, organised vertically
- Layout 3: Dual Mode Landscape: With space for two fragments and a gap for the hinge, organised horizontally
Setting layouts programmatically
Every time there’s a configuration change in our app we need to decide which one of those three layouts needs to be set.
Layouts 2 and 3 are the same layout in a different orientation so we can rely on Android’s native resource by orientation support for this.
In our ActivityonConfigurationChange
we can have something like this:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val layoutId = when {
isDuoDeviceInDualMode() -> R.layout.main_activity_dual_mode
else -> R.layout.main_activity_single_mode
}
setContentView(layoutId)
}
Android will take care of orientation resource provisioning, giving us the correct R.layout.main_activity_dual_mode
layout for portrait or landscape.
Once we have the correct layout set, it’s a matter of displaying the correct fragment (or fragments) in the correct buckets.
Surface DUO SDK
The Duo SDK is not required to build an app able to run in the Surface Duo, but it provides some utilities to make the experience better for the user.
Is the app running in a Surface Duo Device?
DisplayMask: Represents the area of the display that is not functional for displaying content.
If we can create a DisplayMask
then the app is running in a Duo Device. We can use this snippet to determine whether our app is running on a Duo Device. We should check this before setting the activity layout.
fun isDuoDevice(context: Context): Boolean {
return try {
DisplayMask.fromResourcesRectApproximation(context) != null
} catch (ex: Exception) {
false
}
}
Single or Dual Mode?
DisplayMask.getBoundingRectsForRotation(int rotation)
Returns a list of Rects with respect to the rotation, each of which is the bounding rectangle for a non-functional area on the display.
An app is in Dual Mode when it’s presented across both screens. In that case, the hinge will intersect our app window. We can use DisplayMask.getBoundingRectsForRotation()
to obtain the hinge Rect
and check if it intersects our app window’s Rect
fun isDualMode(): Boolean {
val hinge: Rect = getHinge(rotation)
val windowRect: Rect = getWindowRect()
if (windowRect.width() > 0 && windowRect.height() > 0) {
return hinge.intersect(windowRect)
} else {
return false
}
}private fun getHinge(rotation: Int): Rect {
return displayMask
.getBoundingRectsForRotation(rotation)
.let { boundings -> boundings[0] }
}
More resources
- Official documentation: https://aka.ms/SurfaceDuoSDK-Docs
- Surface DUO SDK Feedback forum: https://aka.ms/SurfaceDuoSDK-Feedback
- Surface DUO blog: https://aka.ms/SurfaceDuoSDK-Blog
- Some interesting talks about building Android apps for Dual-screen devices https://developer.microsoft.com/en-us/microsoft-365/virtual-events
This is only a quick look at what the Surface Duo can offer. I’m looking forward to being able to experiment further with the SDK and excited to find out what new experiences will dual-screen devices bring to the Android ecosystem.
Ping me on Twitter if you want to chat more about it, I’d love to hear your thoughts!
Thanks to Cesar Valiente and pablisco for their feedback and help on this post 🙌