(gentle music)
- I’m Kevin Moore. I’m a Product Manager on the Flutter team.
Flutter provides incredible new opportunities
to build apps that run in mobile, desktop,
and the web from a single code base.
However, with these opportunities comes new challenges.
We want our apps to feel familiar to users,
adapting on each platform to maximize usability
and ensure a comfortable and seamless experience.
So, we need to build apps that are not just cross-platform,
but fully platform adaptive.
There are many considerations for developing platform adaptive apps.
Let’s break them into three categories.
First, we'll look at adapting to the size
and shape of the screen our app will be running on.
Next, we need to consider how to support the input methods
being used, such as touch or mouse keyboard.
Lastly, we need to consider the idioms and norms
that users expect on each platform.
We have fairly limited time,
so we're not going to spend too much time on each topic.
My goal is to introduce some of the considerations
for an adaptive app, telling you about the features
that are built into Flutter to help you address them
and to point you at APIs that you can use to fill in the remaining gaps.
Throughout the talk, we’ll use the Flutter Folio app as a reference.
It’s a scrapbooking app that runs on mobile, desktop, and web.
It's free and open source and a demo built to reflect current best practices.
You can go to flutter.dev/go/adaptive to learn more.
(air whooshing)
Let's start by looking at form factor and screen size.
If you've been building apps or websites, you're almost certainly familiar
with creating responsive interfaces.
If you’re a Flutter dev, you know there are a lot
of widgets that make it easy to build resizable layouts.
We certainly don’t have time to go through them all,
but it is worth spending some time perusing
the widget catalog on Flutter.dev to familiarize yourself with the options
that are available out of the box.
If you can’t find a widget that does what you need
you can take a more procedural approach to adjust parameters, swap widgets,
or even rewrite a whole tree.
Here, we switch to a vertical layout when we're on handset.
You’re going to see this approach several times in this talk.
Instead of duplicating code, there are often small tweaks
you can add in-line based on simple conditions.
Here, we inject a different list of widgets
into a column by using the spread operator.
We have a column that already starts out with a header,
but depending on the platform, we inject
either widgets designed for handset or another set.
Dart makes this easy.
Taking a more procedural approach lets you reflow
or even completely restructure your UI to suit a particular form factor.
Thanks to state-based architecture, and since everything in Flutter is a widget,
this is usually simple.
Just instantiate your sub-view into a different parent.
In this example, you can see how the exact same image grid widget
has been presented as a sheet on a handset but as a modal overlay on a larger screen.
Actually determining how you divvy up device layout
will vary between projects, but you’ll usually
use a combination of media query and platform.
It’s a good idea to consolidate this logic
into a single place to keep it consistent,
facilitate testing, and to make updates simpler.
It’s very helpful to have a single source of truth
for numeric values like padding or spacing
which need to be changed throughout the app
depending on the platform.
Here, we’re using a class
with some static values that are easy to update
and reference when you need to change one.
Another option is to use LayoutBuilder
to make decisions based on income and constraints.
You can build a unique layout based on constraints provided
by the parent, in this case choosing between a one
or two-column view based on the available width.
This is less coupled to global variables, which makes it particularly beneficial
for creating reusable components or views that adjust to their local context.
Beyond screen size, we should also consider
the other unique strengths and weaknesses of different form factors.
It’s not always ideal for a multi-platform app
to simply offer identical functionality everywhere.
It may make sense to focus on specific capabilities
or even remove certain features on some device categories.
For example, mobile devices are portable and have cameras
but they aren't well suited for detailed creative work.
So Flutter Folio focuses the handset experience,
which you see on the left, around capturing content
and doing scrapbooks, but it explicitly disables editing features.
Desktop, however, is in the center here, focuses on creation and editing.
The web version on the right uniquely supports
deep links to scrapbooks to leverage the web’s extremely low barrier to sharing.
(air whooshing)
The next thing we need to consider is input devices.
A most obvious thing to consider is touch versus mouse input.
Compared to touch, a mouse provides more precision
which means that hit areas can be smaller
and we can have a higher visual density on the screen.
Flutter’s material design components have visual density baked into them,
allowing you to easily adjust the size of elements
like buttons and list items to accommodate differences between touch and mouse.
The line between these two input models
isn’t always so clear. We have touchscreen laptops
and tablets with styluses.
So Flutter Folio provides a toggle which defaults
the most appropriate setting, but lets the user switch if they’d like.
Of course, precision isn’t the only difference.
You also have to consider additional inputs
like scroll wheel, right-click, hover interactions
and a physical keyboard.
Flutter gives you a lot for free here.
Scrolling widgets, like scroll view or list view,
support the scroll wheel by default.
And because almost every scrollable custom widget
is built using one of these, support comes for free.
Tabbing through controls and activating them
with a keyboard still takes a bit of work
but the focus class and its related widgets make it significantly easier.
Flutter includes multiple ways to handle focused reversal,
from straightforward logic using widget creation order
or the natural reading order, to more hands-on options
like specifying order indices or even writing a totally custom policy.
Material controls include support for basic hover states,
tool tips, and displaying appropriate cursors by default,
but you can tweak these by hand if you like.
For example, here's how easy it is to change the cursor for a specific widget
based on the value of an isbusy flag.
And text fields come support for common interactions
like copy paste via right-click or keyboard shortcuts.
So you get a lot out of the box, but if you build a custom widget
or wanna something unique, Flutter also gives you
low level access to all of these inputs.
For instance, Flutter Folio uses on-pointer signal
to enable custom horizontal scrolling behavior on its main page.
Here, you can see how easy it is to respond to scroll events on any widget
by wrapping it in a listener and responding to pointer scroll events.
And you can also use on secondary tap to display rich context menus on desktop
instead of the larger button controls for touch devices.
Right-click and long press are also interactions
you can listen for with gesture detector just like tap.
The easiest way to start supporting different input devices is to start
with a great touch experience.
You can tweak it to offer higher visual density
when using a mouse and then layer on all the additional inputs.
You can approach these other inputs as accelerators,
alternatives that make a task faster.
The important thing is to consider what a user would expect
when using a particular input device and work to reflect it in your app.
(air whooshing)
Each platform has its own idioms and norms,
nominal or defacto standards which inform user expectations
of how an application should behave.
Thanks in part to the web, users have become accustomed
to more customized experiences, but reflecting
on platform standards provides two significant benefits.
First, following idioms and norms helps you align
with users’ existing mental model.
Accomplishing tasks becomes more intuitive,
and users are more productive and less frustrated.
Second, following idioms and norms builds trust.
Users can become wary or even suspicious
when applications don't adhere to their expectations.
Conversely, a familiar feeling UI can build user trust
and help to improve perception of quality which often adds the benefit of having
better App Store ratings. So how do we achieve this?
You start by asking yourself a couple of questions.
First, what is the expected appearance, presentation,
or behavior on this platform?
And second, how would a user of this platform expect to achieve this goal?
Answering these questions can be hard if you’re not a regular user of that device.
So when possible, try to have someone on the project
that can act as the advocate for each platform.
Ideally they use this as their primary device
and represent the perspective of a highly opinionated user.
On Flutter Folio, one dev was the advocate
for Windows and Android, another for Linux and the web,
and one of the designers gave ongoing feedback on Mac and iOS.
This meant that there was a consistent, informed feedback
on what needed tweaking to make the app feel appropriate for each platform.
As an active participant on the project,
the advocate is well suited to catch nuances in the UI
that feel off on their device.
A simple example is how the default button in a dialogue
is typically displayed on the left on Mac and Linux,
but on the right on Windows.
Details like this are easy to miss
if you're not using a platform on a regular basis.
Let's take a quick spin through a few specific norms
and idioms you might wanna consider
and look at how you could approach these in Flutter.
Scroll bars vary pretty wildly between platforms.
On mobile, they are small visual indicators
that only appear during a scroll. On desktop, they’re larger interactive elements
that can be dragged to scroll and remain visible whenever they are needed.
They even vary between mobile OSs with bouncing overscrolls
in iOS or more sedate interactions on Android.
Thankfully, Flutter handles this for you,
adjusting sides, visibility, interactions,
and scroll physics to match the host platform.
When presenting lists or other collections that support multi-select,
you'll want to adjust your approach for different devices.
Using modifier keys like Shift is common on desktop,
whereas on a touch device, tapping to toggle selection is expected.
Another example is text.
Now Flutter defaults to non selectable text,
which makes sense on mobile, but desktop
and especially web users expect to be able to select and copy text.
This is easy to enable in Flutter by swapping your text widgets for selectable text.
And finally, for those jumping into the newly-released desktop support,
you can even bring customization into the application title bar.
Using the plugin bitsdojo window, Flutter Folio
brings the current user icon and authentication controls
to the top of the window while still reflecting
control conventions across platforms.
That’s just a few of the things you'll wanna consider.
The more operating systems, form factors, and input devices you support,
the more difficult it becomes to spec out every permutation and design.
Taking some time to learn basic usability principles
will empower you to make better decisions.
You'll have less back and forth during production
and you'll end up with a better application faster.
Flutter makes it easy to support multiple platforms
from a single code base.
To do it well, keep these things in mind.
First, take into account form factor and screen size
when determining which features to expose and how.
When considering user input, start with a solid touch implementation,
then layer on support for additional input methods and increased visual density.
And always keep platform idioms and norms in mind.
When an app meets users’ expectations, it will feel at home on their device.
Flutter makes a lot of this super easy, but it’s important to educate yourself
on the basics of usability.
Careful consideration and a bit of planning
will result in apps that are easier to use,
more delightful, and better rated.
To learn more, you can visit flutter.dev/go/adaptive.
There you'll find a source code for the Flutter Folio app,
a link to an article to go deeper into these topics, and other materials
that you can use to get started building apps
that will wow your users.
And be sure to check out all other Flutter talks at I/O. Thanks.
(gentle music)