Your First Bluetooth Low Energy App with Flutter

Flutter App with Bluetooth Low Energy running on a real Android phone, connected to VSCode Debugger

Flutter App with Bluetooth Low Energy running on a real Android phone, connected to VSCode Debugger

📝 4 Jun 2020

Ready to create your very first "Hello World" app with Flutter?

Why not make a sophisticated app that says...

"Hello Bluetooth Low Energy gadgets nearby... Tell me what's inside you!"

With Flutter, Bluetooth LE (Low Energy) apps for Android AND iOS are ridiculously easy to build, let me show you how!

1 Download Flutter SDK

The Flutter SDK works on Windows, macOS and Linux (Intel, not Arm, so Raspberry Pi is no-go).

  1. Download the Flutter SDK

  2. Unzip the Flutter SDK to our Home Directory.

  3. Add flutter/bin to our PATH.

    For macOS and Linux, we may edit ~/.bashrc (or equivalent) and add this...

    export PATH="$PATH:$HOME/flutter/bin"
    
  4. Open a new Command Prompt. Check the Flutter SDK by entering...

    flutter
    

    We should see this helpful message...

Flutter Tool

2 Install Flutter Tools

  1. Download and install VSCode

  2. At the Command Prompt, enter...

    flutter doctor
    

    We will see something like this...

    Flutter Doctor

  3. Whoa that's a long list of complaints! But we shall fix only 3 things: Android Toolchain (or Xcode for iOS), VSCode and Connected Device

Let's fix them now...

3 Flutter for Android

(If you're building for iPhone, skip to the next section)

  1. Android Toolchain: Follow the instructions shown in your screen.

    You may need to run sdkmanager and flutter doctor --android-licenses

  2. VSCode: Launch VSCode. Click View → Extensions

    Install the Flutter Extension for VSCode...

    Flutter Extension for VSCode

  3. Connected Device: Connect our Android phone (with debugging enabled) to the USB port...

    Connect phone to USB port

  4. After connecting our Android phone, we should see the phone in the VSCode status bar...

    Flutter Device in VSCode

  5. At the Command Prompt, enter...

    flutter -v devices
    

    We should see our phone...

    List of devices attached
    99031FFG device usb:3376X product:coral model:Pixel_4_XL device:coral
    Pixel 4 XL • 99031FFG • android-arm64 • Android 10 (API 29)
    
  6. Finally enter...

    flutter doctor
    

    We should see ticks for Flutter, Android Toolchain, VSCode and Connected Device...

    Flutter Doctor After Fixes

  7. We may ignore the other issues for now

4 Flutter for iOS

(If you're building for Android, skip to the next section)

Let's fix 3 things shown below: Xcode, VSCode and Connected Device

Flutter Doctor

  1. Xcode: Follow the instructions shown in your screen.

    You may need to install Xcode, CocoaPods and run xcodebuild

  2. VSCode: Launch VSCode. Click View → Extensions

    Install the Flutter Extension for VSCode...

    Flutter Extension for VSCode

  3. Connected Device: Connect our iPhone to the USB port.

  4. After connecting our iPhone, we should see the phone in the VSCode status bar...

    Flutter on iOS

  5. At the Command Prompt, enter...

    flutter -v devices
    

    We should see our phone...

    1 connected device:
    iPhone 6 Plus • ios • iOS 12.4.6
    
  6. Finally enter...

    flutter doctor
    

    We should see ticks for Flutter, Xcode, VSCode and Connected Device...

    Flutter Doctor After Fixes

  7. We may ignore the other issues for now

5 Download Source Code for Flutter App

The source code for our Flutter app is located here...

github.com/lupyuen/flutter-blue-sample

Our app is derived from the sample app that comes with the flutter_blue Bluetooth LE plugin for Flutter.

  1. In VSCode, click View → Command Palette

  2. Enter Git Clone

  3. Enter https://github.com/lupyuen/flutter-blue-sample

  4. Select a folder to download the source code

  5. When prompted to open the cloned repository, click Open

  6. When prompted to get missing packages, click Get Packages

Check this video for the steps to download the source code for our Flutter app...

6 Debug Flutter App

We're now ready to debug our Flutter app on a real Android or iOS phone!

  1. In VSCode, click Run → Start Debugging

    Start Debugging in VSCode

  2. Select the Dart & Flutter debugger

    Select Debugger in VSCode

  3. Wait for the Flutter app to be compiled and deployed to our phone (May take a minute for the first time)

  4. For iOS: Check the next section for additional Xcode steps

  5. When the Flutter app starts, we'll be able to Scan, Connect, Reload and Expand devices over Bluetooth LE like this...

Scanning for Bluetooth LE devices

And that's our very first Flutter app with Bluetooth LE!

VSCode Debugger has many useful features for debugging Flutter apps. Here's what we see when we hit an unhandled exception in our Flutter app...

Flutter App with VSCode Debugger

Larger image

See this article for more details on building Flutter apps with VSCode, including cool features like Hot Reload...

Here's a video of the steps for debugging a Flutter app with VSCode...

7 Sign Flutter App for iOS

(Skip this section if you're building for Android)

This message appears when we debug our iOS app...

Xcode Signing

Here's what we need to do for iOS...

  1. In VSCode, click Terminal → New Terminal

  2. At the Terminal prompt, enter...

    open ios/Runner.xcworkspace
    

    Open Xcode workspace

  3. In Xcode, click Runner → Targets Runner → Signing & Capabilities

    Xcode Signing

  4. Set Team to our Apple Developer Account

  5. Set Bundle Identifier to a unique name

  6. On our iPhone, click Settings → General → Device Management

  7. Set the Trust Settings like this...

    Trust iOS Developer

We should be able to launch and debug our Flutter app using the instructions from the previous section...

Flutter App on iOS

Here's a demo of our Flutter app on iPhone...

PineTime Smart Watch

PineTime Smart Watch

8 Bluetooth LE Services

Let's connect our Flutter app to a PineTime Smart Watch...

So many Services and Characteristics... What are they?

When we access data and perform functions wirelessly on a Bluetooth LE device (like PineTime), we talk via a Bluetooth LE protocol known as the Generic Attribute (GATT) Profile.

GATT defines the standard way for a Bluetooth LE Client (like our Flutter app) to access a Bluetooth LE Service (like on the PineTime Smart Watch). More about GATT

Our Flutter app displays the GATT Services and GATT Characteristics supported by the Bluetooth LE device. Let's look at the Standard GATT Services that are defined in the Bluetooth LE Specifications...

Bluetooth LE Services on PineTime Smart Watch

  1. Generic Access (0x1800): This GATT Service contains two GATT Charactertistics: Device Name (pinetime) and Appearance. Specifications

  2. Generic Attribute (0x1801): This GATT Services notifies our Flutter app of any changes in PineTime's GATT Services. Specifications

  3. Device Information (0x180A): Contains two GATT Charactertistics: Model Number (Apache Mynewt NimBLE) and Firmware Revision (1.0.0). Specifications

  4. Alert Notification Service (0x1811): For Alerts and Notifications. Specifications

Some GATT Characteristics are shown as a list of numbers...

    Device Name:
    Characteristic 0x2A00
    [112, 105, 110, ...]

The numbers are the ASCII codes for the text strings. We can see the actual strings in the Nordic nRF Connect app below.

Can we do complex things with GATT? Like update the firmware wirelessly on PineTime?

There's a custom GATT Service and Characteristic for that!

Simple Management Protocol (8D53DC1D-1DB7-4CD3-868B-8A527460AA84, shortened to 0xDC1D above) is the GATT Service used by PineTime for updating the firmware.

The GATT Service contains a single GATT Characteristic (DA2E7828-FBCE-4E01-AE9E-261174997C48, shortened to 0x7828 above). Our Flutter app may someday update PineTime's firmware by sending a Write Request to this GATT Characteristic (with the firmware file encoded in the request).

More about Wireless Firmware Update on PineTime

The final GATT Service (59462f12-9543-9999-12c8-58b459a2712d, shorted to 0x2F12 above) is the Security Test Service. More details

For comparison, here are the GATT Services that appear when the Nordic nRF Connect mobile app is connected to PineTime. So our Flutter app really works!

Nordic nRF Connect app connected to PineTime

9 Bluetooth LE Code

I'm new to Flutter and Dart... And I find it absolutely amazing that a few lines of code can do so much!

Our app is structured like this to scan Bluetooth LE devices and display them...

Our App Structure

Here's the code that implements the screen for scanning Bluetooth LE devices: lib/main.dart

//  Screen for finding devices
class FindDevicesScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //  Title for the screen
      appBar: AppBar(
        title: Text('Find Devices'),
      ),

      body: RefreshIndicator(
        //  Start scanning for Bluetooth LE devices
        onRefresh: () =>
            FlutterBlue.instance.startScan(timeout: Duration(seconds: 4)),

        //  List of Bluetooth LE devices
        child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              ...
              StreamBuilder<List<ScanResult>>(
                stream: FlutterBlue.instance.scanResults,
                initialData: [],

                builder: (c, snapshot) => Column(
                  children: snapshot.data
                      .map(
                        //  For each Bluetooth LE device, show the ScanResultTile widget when tapped
                        (r) => ScanResultTile(
                          result: r,
                          onTap: () => Navigator.of(context)
                              .push(MaterialPageRoute(builder: (context) {
                            r.device.connect();
                            return DeviceScreen(device: r.device);
                          })),
                        ),
                      )
                      .toList(),
                ),
              ),
            ...

And here's the code that renders each Bluetooth LE device found: lib/widgets.dart

//  Widget for displaying a Bluetooth LE device
class ScanResultTile extends StatelessWidget {
  ...
  @override
  Widget build(BuildContext context) {
    return ExpansionTile(
      //  Show the device name and signal strength
      title: _buildTitle(context),
      leading: Text(result.rssi.toString()),

      //  Show the Connect button and call onTap when tapped
      trailing: RaisedButton(
        child: Text('CONNECT'),
        color: Colors.black,
        textColor: Colors.white,
        onPressed: (result.advertisementData.connectable) ? onTap : null,
      ),

      //  Display the device's name, power level, manufacturer data, service UUIDs and service data
      children: <Widget>[
        _buildAdvRow(
            context, 'Complete Local Name', result.advertisementData.localName),
        _buildAdvRow(context, 'Tx Power Level',
            '${result.advertisementData.txPowerLevel ?? 'N/A'}'),
        _buildAdvRow(
            context,
            'Manufacturer Data',
            getNiceManufacturerData(
                result.advertisementData.manufacturerData) ??
                'N/A'),
        _buildAdvRow(
            context,
            'Service UUIDs',
            (result.advertisementData.serviceUuids.isNotEmpty)
                ? result.advertisementData.serviceUuids.join(', ').toUpperCase()
                : 'N/A'),
        _buildAdvRow(context, 'Service Data',
            getNiceServiceData(result.advertisementData.serviceData) ?? 'N/A'),
      ],
    );
  }
  ...

Yes the code looks similar to JavaScript. (Because Dart was designed to compile to JavaScript efficiently)

But overall the user interface code looks Declarative and Functional... A huge improvement over JavaScript and React Native!

How do we call the Bluetooth LE functions in our own Flutter app?

Just add the flutter_blue plugin as a dependency like this: pubspec.yaml

dependencies:
  flutter_blue: ^0.7.2

10 What's Next

I'll be using the code in this article to create the open source PineTime Companion App for Android and iOS. So that we can flash our PineTime Smart Watches wirelessly, sync the date and time, show notifications, chart our heart rate, ...

Flutter makes it really easy to maintain a single code base for Android and iOS... And flutter_blue makes Bluetooth LE coding so simple!

If you're keen to help out, come chat with the PineTime FOSS Community (and me) in the PineTime Chatroom!

PineTime Chatroom on Matrix / Discord / Telegram / IRC

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here...

pinetime-rust-mynewt/rust/ app/src/flutter.md

11 Further Reading

"MCUBoot Bootloader for PineTime Smart Watch (nRF52)"

"Firmware Update over Bluetooth Low Energy on PineTime Smart Watch"

"Wireless Firmware Update In Action on PineTime Smart Watch (nRF52)"