Create a Cross-Platform Flutter App for Android and Wear OS (and Others)

Scott Hatfield
14 min readJul 24, 2022

--

Introduction: Building an App for Android and Wear OS

We will build an app that runs on Wear OS and Android from a single codebase.

One of the greatest parts about developing applications with Flutter is that apps can be built for and run on many different platforms from a single codebase. As of Flutter 3.0, the framework supports Android, iOS, web, Windows, MacOS, and Linux. The fact that Flutter allows developers to deploy apps to any of these operating systems with minimal platform-specific code is one of the biggest advantages to writing applications with Flutter versus other development strategies.

But what if you wanted to push the cross-platform compatibility of your Flutter app even further by adding Wear OS to that list of platforms? Imagine creating an app that can, from a single codebase, run on screens of any size ranging from a tiny little smartwatch to a desktop monitor. Well, imagine no longer because today we will be doing exactly that.

In this article, we will build a Flutter app that can run on any of the officially supported platforms, plus Wear OS. We will not just be using the simple “counter” app either. Today we will be building an app that includes all of the elements you are likely to encounter while designing and building an app that could be shipped to the public:

  • The app will use a RESTful API to retrieve information from the Internet.
  • We will include design elements that render depending upon the host device screen size to take advantage of differences in screen real estate.
  • On the Wear OS side, the app will render different UIs depending on whether the watch is in active or ambient mode.
  • Elements in the design will use responsive sizing to scale with the size of the display.
The Partly Windy app running on an Android phone.

Let’s dive into the details and get an introduction to the project.

Project: Partly Windy, for Android and Wear OS

Let’s take a look at the example app we will be using in this article (plus several others on related topics).

We will be making a Flutter app that leverages OpenAI text completion to give us the weather forecast. No more messing with rain gages and those spinning wind speed gadgets, we are going to get our weather forecasts right from the burning heart of computer science achievement, machine learning. The app is named “Partly Windy.”

This app is admittedly a bit satirical and pokes a bit of fun at the tendency for current machine learning technologies to answer seemingly simple questions with wildly inaccurate, and often nonsensical, responses. However, this app will give us the opportunity to explore building for both Wear OS and Android phones from the same codebase. It will also use many of the fundamental system design elements common to cross-platform mobile apps.

Let’s get started.

Prerequisites: Understanding the Approach

Before we hop into the code, let’s take a moment to discuss the approach we will be using to create an app that can be built for either an Android phone or a Wear OS smartwatch from the same codebase. This is actually easier than it might seem at first but it will make the project clearer if we make sure to have the metaphorical picture on the puzzle box before diving in. There are two basic building blocks for the approach we will use in this article:

Add Wear OS Support with the wear plugin

As of the time of this writing (Flutter stable release 3.0.4), Flutter does not officially support Wear OS as one of its target platforms. However, one of the critical parts of the project that you should understand is that Wear OS is basically just a specialized version of Android. The only technical difference between an Android app and a Wear OS app is a couple lines added/modified in the build.gradle and Android Manifest files. You can see the basic setup on the Android developers page about creating a Wear OS app.

We could manually add the required support in our Flutter project, as described in some older discussions on this article’s topic, but to make our lives a little easier, we will instead use the wear plugin for Flutter to handle the task for us. The wear plugin will also provide a couple widgets that will come in very handy for building the Wear OS version of our Flutter app.

Determine the Device Type by its screen size

The second fundamental building block for this project is adding the ability for our Flutter app to determine whether it should load the Wear OS or Android variants of its UI. The app will do this based on the size of the host device. The implementation for this functionality will be based on the LayoutBuilder class, which “builds a widget tree that can depend on the parent widget’s size.” Depending upon the width of the host device, the Flutter app will render either the Wear OS view or the phone view. You could easily extend this logic to render a third layout for tablets or desktops or other large screens; but in this article we will keep things simple by focusing on just two device sizes.

Step 1: Setting Up the Project

If you already have a Flutter project up and running and you are just trying to add support for Wear OS, scroll on down to the next section where we will be working on exactly that. Otherwise, let’s get started by creating a new Flutter project to which we will add the necessary plugins and logic to make it run both on Android and on Wear OS (and on any of the other platforms officially supported by Flutter).

Create a New Flutter Project

Part of the way this project functions is to leverage a strong MVC architecture that will allow our Flutter app to render different views for different screen sizes while keeping all the logical parts of the app the same. The default Counter app you get after running flutter create is not be best when it comes to separation of concerns. Therefore, we can get things started a little more quickly by creating a new project based on the Barebones template, which is already set up with an MVC architecture based on the “WidgetView” pattern.

The Controller part of the MVC architecture shows different views based on the host device screen size.

So, create a new project using the Barebones template, or create a new project using the Counter app and make sure to brush up on the WidgetView pattern.

Connect your Wear OS Smartwatch to Android Studio

Since you are embarking on a moderate-complexity Flutter project, this article assumes you are familiar with connecting your phone to Android Studio so you can build the app onto it and do some debugging. For this project, you will also need to get your Wear OS watch connected.

There are two ways to build and run the Partly Windy app on a Wear OS smartwatch: with a physical smartwatch or with an emulated one.

Set up a Wear OS emulator

Using an emulated smartwatch is easy. First, in Android Studio, start up the Device Manager.

Open Device Manager in Android Studio.

We will be creating a new Wear OS emulator device, so click the Create device button at the top of the Device Manager panel.

Click the Create Device button.

In the Virtual Device Configuration dialog window, on the Select Hardware tab, there is a list of hardware types on the left side of the interface. From this list, select Wear OS. There are four different shape/size combinations among which you can choose so simply select the one that best suites your target audience. It is also quite likely that you will want to set up multiple Wear OS emulators with different shapes and sizes to make sure your app looks its best on a variety of device form factors.

Choose Wear OS from the Category list and select the desired device form factor.

The rest of the setup process for the Wear OS emulated device essentially consists of downloading an OS image and pressing the next button a handful of times. When you are done, you can run the Wear OS emulator inside Android Studio.

Wear OS emulator running in Android Studio.

With your Wear OS emulator running, you can build, install, and launch your Flutter app onto the emulator the same way you would run it on an emulated or physical phone, by selecting the target device and pressing the Run button in the Android Studio toolbar.

Run the app on the Wear OS emulator using the Run button.

Set up a physical Wear OS device

Instead of using an emulator, you can set up a physical Wear OS watch as a target for your app in Android Studio. You may want to use this option if your computer lacks the (not insignificant) processing power and RAM requirements to run a Wear OS emulator. You might also need to do this if your app uses certain device features, like Bluetooth communication, or if the nature of the app you are building requires physical hardware, like some kind of fitness monitor app.

The setup process with a physical Wear OS watch is a bit more complex than the process for an emulator because a Wear OS smartwatch cannot be connected directly to Android Studio. Instead, you will connect the Wear OS watch to your phone, connect your phone to Android Studio, and forward the connection from your phone to your watch. You can find documentation about the process on the Android developers site.

Step 2: Set up the main.dart file

With your new app created, or an existing one loaded up, we can quickly cover how the main.dart file is written to provide some context for the remainder of this article. For this project, main.dart is about as simple as it gets. It sets up light and dark themes, removes the debug banner, and sets the home page.

The DailyForecastRoute widget is the only page in the Partly Windy app and for the rest of the article we will be focusing on building out that page by adding support for Wear OS, loading different views for phones and watches, and building device-specific UIs for Wear OS devices and phones.

Step 3: Add Wear OS Support

I think it’s about time we get into the meat of this article by adding support for Wear OS to our Flutter app. This is actually a lot easier than it might sound at first. There are two steps involved: setting the minSdkVersion and adding the Flutter wear plugin.

Set the minimum Android SDK version

Developing a Wear OS app requires setting the minimum Android SDK version for your app to 23 so we will do that first. In your Flutter app, open up android > app> build.gradle.

Open up the build.gradle file under the android/app directory.

Inside this file, under the defaultConfigelement within the android section you will find the minSdkVersion value set to the default for new Flutter projects. Something like this,

minSdkVersion flutter.minSdkVersion

Go ahead and update that minimum SDK version to 23 like so:

minSdkVersion 23

Add the wear plugin

To build a Wear OS app natively, there are a variety of other items to change in the build.gradle and AndroidManifest.xml files; you can find the full rundown on the Android developers site. However, rather than adding those requirements manually, for this project we will be making use of a Flutter plugin called “wear” (which is in version 1.1.0 at the time of this writing).

The Flutter wear plugin provides project setup and a couple useful Widgets.

The wear plugin will handle adding the required bits and bobs to the Android manifest and gradle files as well as provide a couple useful Widgets that we will discuss in the next section. For now, open up your pubspec.yaml file in the root of your Flutter project directory and add the following line under the dependencies section:

# Adds support and Widgets for Wear OS devices
wear: ^1.1.0

After adding this line, fire off a flutter pub get command in the Android Studio Terminal.

Step 4: Load Different Views for Phones and Watches

Now that our app has support for Wear OS via the wear plugin, the next step is to implement the structure that allows our app to load different views depending upon whether it is running on a phone or a watch.

The app will determine the type of host device by taking a look at its screen size. Devices with screen widths below 300 logical pixels are deemed to be watches and devices with bigger screens are deemed to be phones. The implementation will consist of a LayoutBuilder Widget that provides BoxConstraints, which we will use to look at the screen width and load two different views depending upon the width value. Let’s take a look.

The app loads different views depending upon the screen size.

To put this functionality in context within the larger app, the logic shown in the Gist above to choose a view to display based on the host device screen size is placed within the controllers for each screen. In the case of Partly Windy, the app has a single page, the “daily forecast” page. The logic to choose a view to show is within the DailyForecastController class. There is more going on within that controller, but rendering different views for different devices is one of the main functions.

Step 5: Build the Wear OS Screens

At this point, we’ve added support for Wear OS to our Flutter app and we’ve implemented a mechanism for the app to load different views depending upon the size of the host device. The next step is to build out those views. On the phone side, building the app’s UI is the same process you would use for any other Android-compatible Flutter app. For brevity we will skip that part of the process and instead focus on building the UI for Wear OS devices, which is a little different than the process used for phones.

In addition to setting up the app to support Wear OS, the Flutter wear plugin also provides three useful widgets for building smartphone views:

  • The WatchShape widget determines whether the watch is square or round.
  • The AmbientMode widget is builder that provides the current operational mode of the watch, ambient mode or active mode.
  • The InheritedShape widget can be used to pass the shape of the watch down the widget tree so you don’t have to put WatchShape widgets all over the place.

To build interfaces for the Wear OS version of the app, we will use those three widgets alongside all the other widgets in Flutter’s giant widget catalog. For the Partly Windy app, we will create two pages, one for when the Wear OS device is in ambient mode and one for when it is in active mode. We will create another widget that will determine the host device mode and render these to UIs as needed. So, in other words, we will need three files:

  • The DailyForecastViewPhone widget is where we will find the widgets provided by the wear plugin. This widget will determine the whether the watch is in ambient or active mode and render the appropriate view.
  • The DailyForecastViewWatchAmbient widget uses only standard Flutter widgets and displays a UI with a black background and limited design elements to be shown while the device is in ambient mode. Using a black background reduces the power consumption of the host device while it is in ambient mode.
The app loads different UIs for ambient and active modes.
  • The DailyForecastViewWatchActive widget also uses only standard Flutter widgets and is used while the watch is in active mode (a.k.a interactive mode). This widget uses a colorful background that changes depending upon the time of day to add a little shiny element to the app’s UI.

Loading views depending upon the host device mode

The first widget we will create is the most complex, the one used to render different UIs depending upon whether the watch is in active or ambient mode. To determine the current operational mode of the device, we will use the AmbientMode widget provided by the wear plugin. This widget provides a builder that has the WearMode enum from the wear plugin, which has two values, active or ambient:

enum WearMode { active, ambient }

So, to choose which view to load based on the host device state, we will use a simple conditional statement that looks at the WearMode value. Here’s how the mode-specific UI rendering in the DailyForecastViewPhone.dart file works:

Since the AmbientMode widget is a listener, it will automatically rebuild the screen when the host device mode changes.

Building the ambient mode UI

As shown in the Gist above, the app will load the DailyForecastViewWatchAmbient widget while the watch is in ambient mode. This widget is quite simple. It just shows a Scaffold with a black background along with a simple widget showing the daily forecast generated by a call to an Open AI completions API. This interaction with Open AI is covered in a different article so for now just keep in mind that the WeatherForecast widget consists simply of a Column with two Text widgets inside it. Here is the full DailyForecastViewWatchAmbient widget:

The DailyForecastViewWatchAmbient widget is shown while the watch is in ambient mode.
The Partly Windy app in ambient view on Wear OS.

Building the active mode UI

The view rendered while the watch is in active mode, the DailyForecastViewWatchActive widget, is very similar to the ambient mode widget. The difference is that, with the watch in active mode, the app uses a more decorative background featuring a gradient that changes depending upon the time of day. This background is created by adding one additional widget, the TimeGradient widget to the active mode view. This widget simply wraps the page in a Container with a LinearGradient background.

The Partly Windy app in active mode on Wear OS.

Summing Up

While Flutter does not yet officially support building apps for Wear OS, we can make use of the wear plugin to add support for this operating system in our Flutter projects. Not only this, but we can build apps that can, from a single codebase, be run on or built for just about any operating system. In this article we covered:

  • How to add Wear OS support to Flutter apps using the wear plugin
  • How to load different views for Android phones or Wear OS watches (a method that can be easily extended for any other operating system or screen size your app targets)
  • How to detect the Wear OS host device’s operational mode (ambient or active) and load different UIs as needed
  • How to build screens for Wear OS devices

Hopefully the information in this article will help you include Wear OS support in your next Flutter project and act as a springboard for building even more sophisticated Flutter apps that provide support for many different operating systems and device form factors from a single codebase.

Thanks for reading and happy Fluttering!

--

--

Scott Hatfield

I like to take big, complicated projects, and break them down into simple steps that anybody can understand. https://toglefritz.com/