Introduction to Ionic 4: How Web Components Changed the Game

Introduction to Ionic 4: How Web Components Changed the Game

Ionic is the open-source mobile app development framework that makes it easy to build quality native and progressive web apps with web technologies.

Based on Web Components Ionic is a collection of UI components for building high-quality, cross-platform apps. These components are all built with HTML, CSS, and JavaScript and can easily be deployed natively to iOS and Android devices, desktop with Electron, or to the web as a progressive web application.


Getting Started with Ionic

To install Ionic on your machine, you must have Node installed first, if you have not. Then you can install ionic globally with npm like this:

sudo npm install -g ionic

Now you can navigate into your preferred folder where you want your Ionic application to reside. Start a new app with this command:

ionic start [appName] [option]

appName is the name you want to call your application, option can be one of:

  • tabs: A tabs based layout.
  • sidemenu: A side menu based layout.
  • blank: An empty project with a single page.

Your start command might look like this:

ionic start newApp tabs

Follow the prompts to make a few guided configurations and when it is all setup, you can change directory to the newly created folder and then run your app with this command:

ionic serve

A few days ago, Max and the team at Ionic released the Ionic newest version 4 to all of us and dubbed it Ionic for everyone. In the remaining part of this article, we would take a look at the new features and mostly massive improvements that shipped with this new version.


Web Components

Web components were used to re-write each of the Ionic components in this new version. Web components are a group of Web APIs that have extensive support on modern browsers both on mobile devices and desktops. Some of these APIs include shadow DOM and custom elements which has started getting widespread adoption. Web components makes the browser handle more of the work with less code from you. Big improvements in load time and performance is also very ideal for progressive web applications. Ionic polyfills browsers that do not support web components yet so you do not have to ever worry about browser support.


Framework Agnostic

Ionic’s very first and subsequent versions were focused on the Angular js framework and the team consistently kept up to the phases and updates of Angular. However, the dream was always to build mobile apps using web technology, no matter your preferred framework. In this new version, thanks to custom elements, you can use Ionic components which are basically used as HTML tags in any framework of your choice. Yes, you heard correctly. For Angular devs, nothing about the syntax you already use changes. Just like Ionic Angular, there is now the React and Vue versions, Ionic Vue and Ionic React and soon more resources and guides are going to be available to work with them.


Adapting Angular Core Features

Initially when Ionic 2 was in the beginner days and was built for Angular applications, there was a cloud of uncertainty around Angular and support for tooling so the Ionic team built their own in-house CLI tools, build tools and router. But now that the Angular team has taken up these things as their priority, the Ionic team is embracing the Angular CLI and Router tools from Angular .

To this effect, ionic-app-scripts is now replaced with the official Angular tooling. So Angular devs can use the Angular CLI directly and evolve with these core tools overtime. While this will be a change for existing Ionic Angular developers, it is a long term win for the project as Ionic can focus more on components and less on unnecessary, complex tooling, and a win for Ionic Angular developers that can now use first-class Angular tooling and conventions.


Shiny New Docs

One of the best news and my favourite too is the new documentation. Ionic version 4.0 has a revamped documentation, Ionic is already well known just like Vue for super easy docs and now the team has added even more improvements to accommodate v4, they call it the 0.5 major release work-in-progress. This documentation, along with dramatically improved performance also includes a simpler design, focused on content, and an easier-to-navigate organisation and code snippets. The components and the API references were also made concise enough for easy understanding. Check it out here.

Additionally, the Ionic CLI tool also got an overhaul to provide more information in a cleaner and easier to read layout. Check out the new CLI Docs

Refactored CLI 4.0

Ionic 4.0 also ships with a refactored CLI Version 4.0. Ionic CLI is used to develop Ionic apps and is designed to work alongside the Angular CLI so you can get the very best from both. It is faster and cooler than ever, providing powerful Cordova integration with live-reload, custom schematics for generators, and support for multiple projects out of the box.


Bring Your Own Framework Party

Thanks to Web Components APIs, you can now use Ionic in not just only your Angular Applications but in Vue and React and other frameworks. Although there is need to make the processes as smooth as it is in Angular, where features like routing has been handled by the framework itself. So in the coming weeks, expect Vue or React specific functionality to be released. Official Vue.js and React bindings are currently in alpha and are expected to be a big part of the Ionic 4 developer experience henceforth.


Ionicons 4.0

Ionicons are the official icons pack by the ionic team. This new version is available and distributed as web components with drastically reduced sizes, and brand new icon forms reflecting the latest iOS and Material Design styles.


PWAs on Steroids

Ionic prides itself as the best UI framework for building high performant Progressive Web Applications. To achieve this and make astonishingly excellent lighthouse scores, the team at Ionic built out a web component pipeline using Stencil to render Ionic components, ensuring they were tightly packed, lazy loaded, and delivered in smart collections consisting of components developers use. Stencil-built components rank very high in lighthouse audits.


Native API Upgrade

With this new Ionic version, Ionic Native 5.0 Beta has been upgraded to also be framework independent. You can now use the wrappers outside of Angular as simple classes while still offering Angular providers that work with dependency injection. Check out the Docs here


Customisations with CSS Variables

Another big change made in this new version of Ionic was to start using CSS Variables in each component. These variables use a public theming API that provides developers with structure to customise components in Ionic in a way that supports easily upgrading to future versions of Ionic without introducing compatibility issues. They also provide a standardised way to theme Ionic apps independent of the frontend framework used in your application. You can modify the overall look and feel of your app by just changing a few variables, all without build tools.


Migrating from 3.0 to 4.0

You can migrate your ionic application to version 4.0 in the following steps:

  1. Generate a new project using the blank starter (as described at the start of this article)
  2. Copy any Angular services from src/providers to src/app/services
  • Services should include { providedIn: 'root' } in the @Injectable()decorator. For details, please see Angular provider docs.

3. Copy the app’s other root level items (pipes, components, etc) keeping in mind that the directory structure changes from src/components to src/app/components, etc.

4. Copy global Sass styling from src/app/app.scss to src/global.scss

5. Copy the rest of the application, page by page or feature by feature, keeping the following items in mind:

  • Emulated Shadow DOM is turned on by default
  • Page/component Sass should no longer be wrapped in the page/component tag and should use Angular’s [styleUrls](https://angular.io/api/core/Component#styleUrls) option of the @Componentdecorator
  • RxJS has been updated from v5 to v6 (see RxJS Changes)
  • Certain lifecycle hooks should be replaced by Angular’s hooks (see Lifecycle Events)
  • Markup changes may be required (migration tool available, see Markup Changes)
In many cases, using the Ionic CLI to generate a new object and then copying the code also works very well.

Conclusion

We have thoroughly looked at the new version of Ionic and everything it shipped with and what it means for us Ionic developers. We saw how web components is now in the forefront of democratizing mobile development across browsers. Upgrade your ionic application to 4 today and enjoy.

Learn more

Building a mobile chat app with Nest.js and Ionic 4

Android Studio for beginners

Getting started with Flutter

Learn Swift 4: From Beginner to Advanced

Originally published by Nwose Lotanna at https://blog.bitsrc.io

Flutter - State Management using PROVIDER

Flutter - State Management using PROVIDER

In this tutorial you will see the very basics of implementing "Provider" for State management in your Flutter Applications.

In this tutorial you will see the very basics of implementing "Provider" for State management in your Flutter Applications.

So Let’s get started

Before looking into providers lets see whatsis ChangeNotifier this plugin uses ChangeNotifier to to listen and update any changes.

What is ChangeNotifier

Form docs

A class that can be extended or mixed in that provides a change notification API using [VoidCallback] for notifications.> [ChangeNotifier] is optimized for small numbers (one or two) of listeners. It is O(N) for adding and removing listeners and O(N²) for dispatching notifications (where N is the number of listeners)##

Provider

Existing providers

provider exposes a few different kinds of "provider" for different types of objects.

Let's get started with our code

first things first let add plugin to pubspec.yaml

provider: ^2.0.1
http: ^0.12.0+2

Let's Write our provider class first we name it AppState

import 'package:flutter/material.dart';

class AppState with ChangeNotifier {
  AppState();

  String _displayText = "";

  void setDisplayText(String text) {
    _displayText = text;
    notifyListeners();
  }

  String get getDisplayText => _displayText;
}

Our AppState is extended with ChangeNotifier which is used to notify its listeners when we call notifyListeners()

In the code, we declared two methods setDisplayText and getDisplayText which are used to read and write the value in our state

Now we move to our main.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo_provider/app_state.dart';
import 'package:flutter_demo_provider/text_display.dart';
import 'package:flutter_demo_provider/text_edit.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: ChangeNotifierProvider<AppState>(
          builder: (_) => AppState(),
          child: MyHomePage(),
        ));
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        padding: const EdgeInsets.all(16.0),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              TextDisplay(),
              TextEditWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

In the code, we can notice we have used ChangeNotifierProvider which is provided from out provider plugin

It accepts two parameters one is builder and the other is child

return MaterialApp(
 title: 'Flutter Demo',
 theme: ThemeData(
 primarySwatch: Colors.blue,
 ),
 home: ChangeNotifierProvider<AppState>(
 builder: (_) => AppState(),
 child: MyHomePage(),
 ));
}

Inside the MyHomePage we have a Scaffold with Column which has two Widgets TextDisplay() and TextEditWidget()

TextDisplay(),
TextEditWidget(),

Here is out TextDisplay() in text_display.dart

import 'package:flutter/material.dart';
import 'package:flutter_demo_provider/app_state.dart';
import 'package:provider/provider.dart';

class TextDisplay extends StatefulWidget {
  @override
  _TextDisplayState createState() => _TextDisplayState();
}

class _TextDisplayState extends State<TextDisplay> {
  @override
  Widget build(BuildContext context) {
    final appState = Provider.of<AppState>(context);

    return Container(
      padding: const EdgeInsets.all(16.0),
      child: Text(
        appState.getDisplayText,
        style: TextStyle(
          fontSize: 24.0,
        ),
      ),
    );
  }
}

In the above code we see

Widget build(BuildContext context) {
 final appState = Provider.of<AppState>(context);

 return Container(
 padding: const EdgeInsets.all(16.0),
 child: Text(
 appState.getDisplayText,
 style: TextStyle(
 fontSize: 24.0,
 ),
 ),
 );
}
final appState = Provider.of<AppState>(context);

This above line of code will get the provider for listening for any changes optionally we can also opt-out for listening by proving listen: false

final appState = Provider.of<AppState>(context, listen: false);

Now in order to access text, we have a function in our provider called getDisplayText

appState.getDisplayText()

Here is out TextEditWidget() in text_edit.dart

  
import 'package:flutter/material.dart';
import 'package:flutter_demo_provider/app_state.dart';
import 'package:provider/provider.dart';

class TextEditWidget extends StatefulWidget {
  @override
  _TextEditWidgetState createState() => _TextEditWidgetState();
}

class _TextEditWidgetState extends State<TextEditWidget> {
  TextEditingController _textEditingController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final appState = Provider.of<AppState>(context);

    return Container(
      child: TextField(
        controller: _textEditingController,
        decoration: InputDecoration(
          labelText: "Some Text",
          border: OutlineInputBorder(),
        ),
        onChanged: (changed) => appState.setDisplayText(changed),
        onSubmitted: (submitted) => appState.setDisplayText(submitted),
      ),
    );
  }
}

In the above code, we get our appState inside the build function

final appState = Provider.of<AppState>(context);

In order to manipulate the text in the state we call setDisplayText(text) function

TextField(
 controller: _textEditingController,
 decoration: InputDecoration(
 labelText: "Some Text",
 border: OutlineInputBorder(),
 ),
 onChanged: (changed) => appState.setDisplayText(changed),
 onSubmitted: (submitted) => appState.setDisplayText(submitted),
)

we are updating the state whenever out text is changes

onChanged: (changed) => appState.setDisplayText(changed)

Now we perform network operation

Now inside our app state, we have some additional functions and variables

String _dataUrl = "https://reqres.in/api/users?per_page=20";
String _jsonResonse = "";
bool _isFetching = false;

bool get isFetching => _isFetching;

Future<void> fetchData() async {
 _isFetching = true;
 notifyListeners();

 var response = await http.get(_dataUrl);
 if (response.statusCode == 200) {
 _jsonResonse = response.body;
 }

 _isFetching = false;
 notifyListeners();
}

String get getResponseText => _jsonResonse;

List<dynamic> getResponseJson() {
 if (_jsonResonse.isNotEmpty) {
 Map<String, dynamic> json = jsonDecode(_jsonResonse);
 return json['data'];
 }
 return null;
}

Here we have a few more functions fetchData, getResponseText and getResponseJson

fetchData will perform the network operation and update the variable with the response data (you can parse your JSON to custom model here and save it in a List)

getResponseText will return the plain text response

getResponseJson will convert the response text to a Map and returndata field inside it which is a list of Map

To see the final app_state.dart visit below link

Now inside out MyHomePage widget we add two more widgets to our column

RaisedButton(
 onPressed: () => appState.fetchData(),
 child: Text("Fetch Data from Network"),
),
ResponseDisplay(),

So I am calling appState.fetchData() whenever I press the button now fetchData will take care of all the updating of the state on Github

Here is our ResponseDisplay Widget named response_display.dart

  
import 'package:flutter/material.dart';
import 'package:flutter_demo_provider/app_state.dart';
import 'package:provider/provider.dart';

class ResponseDisplay extends StatefulWidget {
  @override
  _ResponseDisplayState createState() => _ResponseDisplayState();
}

class _ResponseDisplayState extends State<ResponseDisplay> {
  @override
  Widget build(BuildContext context) {
    final appState = Provider.of<AppState>(context);

    return Container(
      padding: const EdgeInsets.all(16.0),
      child: appState.isFetching
          ? CircularProgressIndicator()
          : appState.getResponseJson() != null
              ? ListView.builder(
                  primary: false,
                  shrinkWrap: true,
                  itemCount: appState.getResponseJson().length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      leading: CircleAvatar(
                        backgroundImage: NetworkImage(
                            appState.getResponseJson()[index]['avatar']),
                      ),
                      title: Text(
                        appState.getResponseJson()[index]["first_name"],
                      ),
                    );
                  },
                )
              : Text("Press Button above to fetch data"),
    );
  }
}

Here in the above code we parse the JSON and build a list of data

To get the code it's here on GitHub

Flutter - GPS Geolocation Tutorial

Flutter - GPS Geolocation Tutorial

This tutorial shows you how to access device location in Flutter using GPS, including how to get permissions, get current location and continuous location update.

This tutorial shows you how to access device location in Flutter using GPS, including how to get permissions, get current location and continuous location update.

GPS has become a standard feature on modern smartphones. It's usually used by applications to get the device location.

Dependencies

A Flutter package geolocator provides geolocation functionalities. Add it in your pubspec.yaml file and run Get Packages.

  dependencies {
    ...
    geolocator: ^3.0.1
    ...
  }

Permissions

You need to add permissions to each platform. For Android, you need ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION. Add the following inAndroidManifest.xml.

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

For iOS, you need NSLocationWhenInUseUsageDescription permission. Add it in the Info.plist file.

  <key>NSLocationWhenInUseUsageDescription</key>
  <string>This app needs access to location when open.</string>

Code Example

Below is the code structure for this tutorial. We need to create an instance of Geolocator and store the value of latest Position. The application will use the Position value to display the latitude and the longitude.

  import 'dart:async';
  import 'package:flutter/material.dart';
  import 'package:geolocator/geolocator.dart';

  class GeolocationExampleState extends State<GeolocationExample> {
    Geolocator _geolocator;
    Position _position;

    @override
    void initState() {
      super.initState();

      _geolocator = Geolocator();
    }

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Flutter Geolocation Example'),
        ),
        body: Center(
            child: Text(
               'Latitude: ${_position != null ? _position.latitude.toString() : '0'},'
                  ' Longitude: ${_position != null ? _position.longitude.toString() : '0'}'
            )
        ),
      );
    }
  }

Check Permission

If you’ve added the right permissions, the application will be granted with permissions to access the device location using GPS. In Android 6.0 and above, it will ask the user to grant the permission. But if you need to check whether the application has permission to access location, you can do it programatically. To do so, use checkGeolocationPermissionStatus method which returns Future. Optionally, for iOS, you can check locationAlways and locationWhenInUse separately by passing locationPermission optional parameter whose type is GeolocationPermission

  void checkPermission() {
    _geolocator.checkGeolocationPermissionStatus().then((status) { print('status: $status'); });
    _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationAlways).then((status) { print('always status: $status'); });
    _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationWhenInUse)..then((status) { print('whenInUse status: $status'); });
  }

Get Current Location

Getting the current location is very simple. Just use getCurrentPosition method which returns Future<Location>. You can pass desiredAccuracy option.

  await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high)

Sometimes the process of getting current location may fail, for example if the user turns off the GPS sensor. If it has been turned of since the beginning, it may cause error, so we need to catch the error. There’s also possible the GPS is turned off while the process of finding location is on going. On this case, it may cause the process stuck, and therefore it’s better to add execution timeout.

  void updateLocation() async {
    try {
      Position newPosition = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
          .timeout(new Duration(seconds: 5));

      setState(() {
        _position = newPosition;
      });
    } catch (e) {
      print('Error: ${e.toString()}');
    }
  }

Below are the descriptions of each LocationAccuracy value.

Name Description lowest Location is accurate within a distance of 3000m on iOS and 500m on Android. low Location is accurate within a distance of 1000m on iOS and 500m on Android. medium Location is accurate within a distance of 10m on iOS and between 0m and 100m on Android. high Location is accurate within a distance of 10m on iOS and between 0m and 100m on Android. best Location is accurate within a distance of ~0m on iOS and between 0m and 100m on Android. bestForNavigation Location is accuracy is optimized for navigation on iOS and matches LocationAccuracy.best on Android. Location Update Stream

To get the updated location, actually you can put the code above in a while loop. But, there’s a better and cleaner way. You can use getPositionStream which returns Stream<Subscription>. You can also set how much location change is needed before the listener is notified using distanceFilter.

  LocationOptions locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 1);

  StreamSubscription positionStream = _geolocator.getPositionStream(locationOptions).listen(
            (Position position) {
          _position = position;
        });

Below is the full code of this tutorial

  import 'dart:async';
  import 'package:flutter/material.dart';
  import 'package:geolocator/geolocator.dart';

  void main() => runApp(MyApp());

  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Geolocation',
        home: GeolocationExample(),
      );
    }
  }

  class GeolocationExampleState extends State {
    Geolocator _geolocator;
    Position _position;

    void checkPermission() {
      _geolocator.checkGeolocationPermissionStatus().then((status) { print('status: $status'); });
      _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationAlways).then((status) { print('always status: $status'); });
      _geolocator.checkGeolocationPermissionStatus(locationPermission: GeolocationPermission.locationWhenInUse)..then((status) { print('whenInUse status: $status'); });
    }

    @override
    void initState() {
      super.initState();

      _geolocator = Geolocator();
      LocationOptions locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 1);

      checkPermission();
  //    updateLocation();

      StreamSubscription positionStream = _geolocator.getPositionStream(locationOptions).listen(
              (Position position) {
            _position = position;
          });
    }

    void updateLocation() async {
      try {
        Position newPosition = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
            .timeout(new Duration(seconds: 5));

        setState(() {
          _position = newPosition;
        });
      } catch (e) {
        print('Error: ${e.toString()}');
      }
    }

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Startup Name Generator'),
        ),
        body: Center(
            child: Text(
                'Latitude: ${_position != null ? _position.latitude.toString() : '0'},'
                    ' Longitude: ${_position != null ? _position.longitude.toString() : '0'}'
            )
        ),
      );
    }
  }

  class GeolocationExample extends StatefulWidget {
    @override
    GeolocationExampleState createState() => new GeolocationExampleState();
  }

Flutter: Adding Bluetooth Functionality

Flutter: Adding Bluetooth Functionality

This article will help you use Bluetooth functionality with Flutter.

This article will help you use Bluetooth functionality with Flutter.

Introduction:

There is little documentation to no documentation on using Bluetooth in Flutter. In this article, I will help you by demonstrating some basic concepts needed to implement Bluetooth functionality in your app.

Firstly, plugin/dependency we will be using in this app to add Bluetooth is “flutter_bluetooth_serial”, this plugin is implemented from another parent plugin called “flutter_blue”. This is a very new plugin, the only plugin for bluetooth available as of now. It contains a few bugs but trust me, this will surely get your job done for most basic projects.

Note: Before we go any further, it is worth mentioning that this plugin will only work for Android### Implementation:

Add this dependency in your “pubspec.yaml” file :

dependencies:
flutter_bluetooth_serial: ^0.0.4

In the “main.dart” file the base code of the app will look like this:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BluetoothApp(), // BluetoothApp() would be defined later 
    );
  }
}

Now, let’s create a StatefulWidget called “BluetoothApp”. In _BluetoothAppState, we need to define some variables and a Key. We also have to get an instance of FlutterBluetoothSerial in this class. This class will allow us to control and retrieve Bluetooth information.

class BluetoothApp extends StatefulWidget {
  @override
  _BluetoothAppState createState() => _BluetoothAppState();
}

class _BluetoothAppState extends State<BluetoothApp> {
  // Initializing a global key, as it would help us in showing a SnackBar later
  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
  // Get the instance of the bluetooth
  FlutterBluetoothSerial bluetooth = FlutterBluetoothSerial.instance;

  // Define some variables, which will be required later
  List<BluetoothDevice> _devicesList = [];
  BluetoothDevice _device;
  bool _connected = false;
  bool _pressed = false;

  @override
  Widget build(BuildContext context) {
    return Container(
      // We have to work on the UI in this part
    );
  }
}

Now, it’s time for implementing the critical portion of the app. We have to get the list of Paired Bluetooth devices and check whether the Bluetooth is connected. This is done asynchronously. We also have to create a list of devices, to be shown in the UI later.

These operations should be done in a “Future” method, which should be called from initState().

class _BluetoothAppState extends State<BluetoothApp> {
  ...

  @override
  void initState() {
    super.initState();
    bluetoothConnectionState();
  }

  // We are using async callback for using await
  Future<void> bluetoothConnectionState() async {
    List<BluetoothDevice> devices = [];

    // To get the list of paired devices
    try {
      devices = await bluetooth.getBondedDevices();
    } on PlatformException {
      print("Error");
    }

    // For knowing when bluetooth is connected and when disconnected
    bluetooth.onStateChanged().listen((state) {
      switch (state) {
        case FlutterBluetoothSerial.CONNECTED:
          setState(() {
            _connected = true;
            _pressed = false;
          });

          break;

        case FlutterBluetoothSerial.DISCONNECTED:
          setState(() {
            _connected = false;
            _pressed = false;
          });
          break;

        default:
          print(state);
          break;
      }
    });

    // It is an error to call [setState] unless [mounted] is true.
    if (!mounted) {
      return;
    }

    // Store the [devices] list in the [_devicesList] for accessing
    // the list outside this class
    setState(() {
      _devicesList = devices;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      // We have to work on the UI in this part
    );
  }
}

Time to move on to the UI , the most beautiful part of Flutter coding. The code would be a little bit long but it would mostly contain easily readable code, if you are somewhat familiar with the Flutter Widgets. After completing this UI, we have to implement some methods.

...
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        key: _scaffoldKey,
        appBar: AppBar(
          title: Text("Flutter Bluetooth"),
          backgroundColor: Colors.deepPurple,
        ),
        body: Container(
          // Defining a Column containing FOUR main Widgets wrapped with some padding:
          // 1. Text
          // 2. Row
          // 3. Card
          // 4. Text (wrapped with "Expanded" and "Padding")
          child: Column(
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.only(top: 8.0),
                child: Text(
                  "PAIRED DEVICES",
                  style: TextStyle(fontSize: 24, color: Colors.blue),
                  textAlign: TextAlign.center,
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                // Defining a Row containing THREE main Widgets:
                // 1. Text
                // 2. DropdownButton
                // 3. RaisedButton
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Text(
                      'Device:',
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    DropdownButton(
                      // To be implemented : _getDeviceItems()
                      items: _getDeviceItems(),
                      onChanged: (value) => setState(() => _device = value),
                      value: _device,
                    ),
                    RaisedButton(
                      onPressed:
                          // To be implemented : _disconnect and _connect
                          _pressed ? null : _connected ? _disconnect : _connect, 
                      child: Text(_connected ? 'Disconnect' : 'Connect'),
                    ),
                  ],
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Card(
                  elevation: 4,
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    // Defining a Row containing THREE main Widgets:
                    // 1. Text (wrapped with "Expanded")
                    // 2. FlatButton
                    // 3. FlatButton
                    child: Row(
                      children: <Widget>[
                        Expanded(
                          child: Text(
                            "DEVICE 1",
                            style: TextStyle(
                              fontSize: 20,
                              color: Colors.green,
                            ),
                          ),
                        ),
                        FlatButton(
                          onPressed:
                              // To be implemented : _sendOnMessageToBluetooth()
                              _connected ? _sendOnMessageToBluetooth : null,
                          child: Text("ON"),
                        ),
                        FlatButton(
                          onPressed:
                              // To be implemented : _sendOffMessageToBluetooth()
                              _connected ? _sendOffMessageToBluetooth : null,
                          child: Text("OFF"),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(20),
                  child: Center(
                    child: Text(
                      "NOTE: If you cannot find the device in the list, "
                      "please turn on bluetooth and pair the device by "
                      "going to the bluetooth settings",
                      style: TextStyle(
                          fontSize: 15,
                          fontWeight: FontWeight.bold,
                          color: Colors.red),
                    ),
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
}

So, now it’s time for implementing the remaining methods. At first let us start with the _getDeviceItems() method.

  ...
  // Create the List of devices to be shown in Dropdown Menu
  List<DropdownMenuItem<BluetoothDevice>> _getDeviceItems() {
    List<DropdownMenuItem<BluetoothDevice>> items = [];
    if (_devicesList.isEmpty) {
      items.add(DropdownMenuItem(
        child: Text('NONE'),
      ));
    } else {
      _devicesList.forEach((device) {
        items.add(DropdownMenuItem(
          child: Text(device.name),
          value: device,
        ));
      });
    }
    return items;
}

With the UI out of the way, we are left with four methods. For this example, we will be implementing the connect and disconnect methods. We’ll also implement a method to display a “SnackBar” to the user if there are no Bluetooth device is selected when the user tries to connect.

...
// Method to connect to bluetooth
  void _connect() {
    if (_device == null) {
      show('No device selected');
    } else {
      bluetooth.isConnected.then((isConnected) {
        if (!isConnected) {
          bluetooth
              .connect(_device)
              .timeout(Duration(seconds: 10))
              .catchError((error) {
            setState(() => _pressed = false);
          });
          setState(() => _pressed = true);
        }
      });
    }
  }

  // Method to disconnect bluetooth
  void _disconnect() {
    bluetooth.disconnect();
    setState(() => _pressed = true);
  }
  
  // Method to show a Snackbar,
  // taking message as the text
  Future show(
    String message, {
    Duration duration: const Duration(seconds: 3),
  }) async {
    await new Future.delayed(new Duration(milliseconds: 100));
    _scaffoldKey.currentState.showSnackBar(
      new SnackBar(
        content: new Text(
          message,
        ),
        duration: duration,
      ),
    );
  }
...

At this point, we are almost finished. We are now left with two methods, one for sending a message to turn on Bluetooth and the other for sending a message to turn off Bluetooth.

  ...
  // Method to send message,
  // for turning the bletooth device on
  void _sendOnMessageToBluetooth() {
    bluetooth.isConnected.then((isConnected) {
      if (isConnected) {
        bluetooth.write("1");
        show('Device Turned On');
      }
    });
  }

  // Method to send message,
  // for turning the bletooth device off
  void _sendOffMessageToBluetooth() {
    bluetooth.isConnected.then((isConnected) {
      if (isConnected) {
        bluetooth.write("0");
        show('Device Turned Off');
      }
    });
  }
...

That’s it! the Dart code required to make this work is now complete. That said, if we try running our app it will crash:

To fix this, we need to add the sdk to the AndroidManifest. Navigate to your project folder and follow these steps: android -> app -> src -> main -> AndroidManifest.xml

Add these two lines of code in your “AndroidManifest.xml” file :

<manifest ...
    <!-- Add this line (inside manifest tag) -->
    xmlns:tools="http://schemas.android.com/tools">
    
    <!-- and this line (outside manifest tag) -->
    <uses-sdk tools:overrideLibrary="io.github.edufolly.flutterbluetoothserial"/>
    ....

</manifest>

Conclusion:

As I said at the beginning of this article, this plugin contains some bugs and is still under development.

Below are some screenshots showing various phases. If the user doesn’t have permission, the first thing the user will see is a prompt to grant the app location access. This is completely normal, just click “Allow” and everything should be fine.

Screenshots:

You are free to modify the code to add more functionality to the app.

The GitHub repo link for this project is here

If you like this project, please give “Stars” in my GitHub repo. Thank you for reading, if you enjoyed the article make sure to show me some love by hitting that clap button!

Happy coding…

Learn More

Getting started with Flutter

Flutter Tutorial - Flight List UI Example In Flutter

Let’s Develop a Mobile App in Flutter

Mastering styled text in Flutter

A Design Pattern for Flutter

Weather App with “flutter_bloc”

How to integrate your iOS Flutter App with Firebase on MacOS

An introduction to Dart and Flutter

Learn Flutter & Dart to Build iOS & Android Apps

Flutter & Dart - The Complete Flutter App Development Course

Dart and Flutter: The Complete Developer’s Guide

Flutter - Advanced Course

Push Notification using Ionic 4 and Firebase Cloud Messaging

Push Notification using Ionic 4 and Firebase Cloud Messaging

The comprehensive step by step tutorial on receiving a push notification on Mobile App using Ionic 4 and Firebase Cloud Messaging (FCM)

The comprehensive step by step tutorial on receiving a push notification on Mobile App using Ionic 4 and Firebase Cloud Messaging (FCM). We will use Ionic 4 Cordova native FCM plugin for receiving a push notification and using Firebase API for sending push notification from the Postman.

Table of Contents:

The following tools, frameworks, and modules are required for this tutorial:

Before going to the main steps, we assume that you have to install Node.js. Next, upgrade or install new Ionic 4 CLI by open the terminal or Node command line then type this command.

sudo npm install -g ionic

You will get the latest Ionic CLI in your terminal or command line. Check the version by type this command.

ionic --version
4.10.3

1. Setup and Configure Google Firebase Cloud Messaging

Open your browser then go to Google Firebase Console then login using your Google account.

Next, click on the Add Project button then fill the Project Name with Ionic 4 FCM and check the terms then click Create Project button.

After clicking the continue button you will redirect to the Project Dashboard page. Click the Gear Button on the right of Project Overview then click Project Settings. Click the Cloud Messaging tab the write down the Server Key and Sender ID for next usage in the API and Ionic 4 App. Next, back to the General tab then click the Android icon in your Apps to add Android App.

Fill the required fields in the form as above then click Register App button. Next, download the google-services.json that will use in the Ionic 4 app later. Click next after download, you can skip Add Firebase SDK by click again Next button. You can skip step 4 if there’s no App creating on running yet.

2. Create a new Ionic 4 App

To create a new Ionic 4 App, type this command in your terminal.

ionic start ionic4-push blank --type=angular

If you see this question, just type N for because we will installing or adding Cordova later.

Install the free Ionic Appflow SDK and connect your app? (Y/n) N

Next, go to the newly created app folder.

cd ./ionic4-push

As usual, run the Ionic 4 App for the first time, but before run as lab mode, type this command to install @ionic/lab.

npm install --save-dev @ionic/lab
ionic serve -l

Now, open the browser and you will the Ionic 4 App with the iOS, Android, or Windows view. If you see a normal Ionic 4 blank application, that’s mean you ready to go to the next steps.

3. Add Ionic 4 Cordova Native FCM Plugin

To install Ionic 4 Cordova Native Firebase Message Plugin, type this command.

ionic cordova plugin add cordova-plugin-fcm-with-dependecy-updated
npm install @ionic-native/fcm

Next, open and edit src/app/app.module.ts then add this import.

import { FCM } from '@ionic-native/fcm/ngx';

Add to @NgModule providers.

providers: [
&nbsp; StatusBar,
&nbsp; SplashScreen,
&nbsp; FCM,
&nbsp; { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],

Next, open and edit src/app/app.component.ts then add this import.

import { FCM } from '@ionic-native/fcm/ngx';
import { Router } from '@angular/router';

Inject FCM and Router module to the constructor.

constructor(
&nbsp; private platform: Platform,
&nbsp; private splashScreen: SplashScreen,
&nbsp; private statusBar: StatusBar,
&nbsp; private fcm: FCM,
&nbsp; private router: Router
) {
&nbsp; this.initializeApp();
}

Inside platform ready of initializeApp function, add a function to get FCM token then print out to the browser console.

this.fcm.getToken().then(token => {
&nbsp; console.log(token);
});

Add this function to refresh the FCM token.

this.fcm.onTokenRefresh().subscribe(token => {
&nbsp; console.log(token);
});

Add this function to receive push notification from Firebase Cloud Messaging.

this.fcm.onNotification().subscribe(data => {
&nbsp; console.log(data);
&nbsp; if (data.wasTapped) {
&nbsp; &nbsp; console.log('Received in background');
&nbsp; &nbsp; this.router.navigate([data.landing_page, data.price]);
&nbsp; } else {
&nbsp; &nbsp; console.log('Received in foreground');
&nbsp; &nbsp; this.router.navigate([data.landing_page, data.price]);
&nbsp; }
});

Above example of receiving a push notification from FCM will redirect to the other page with params of data. For that, next, we have to add a new page by type this command.

ionic g page second

Next, modify src/app/app-routing.module.ts then change the new page route.

const routes: Routes = [
&nbsp; { path: '', redirectTo: 'home', pathMatch: 'full' },
&nbsp; { path: 'home', loadChildren: './home/home.module#HomePageModule' },
&nbsp; { path: 'second/:price', loadChildren: './second/second.module#SecondPageModule' },
];

Next, open and edit src/app/second/second.page.ts then add this import.

import { ActivatedRoute } from '@angular/router';

Inject that module to the constructor.

constructor(private route: ActivatedRoute) { }

Add a variable for hold data from router parameters.

price: any = '';

Add this line to get data from the router parameters.

constructor(private route: ActivatedRoute) {
&nbsp; this.price = this.route.snapshot.params['price'];
}

Next, open and edit src/app/second/second.page.html then replace all HTML tags with this.

<ion-header>
&nbsp; <ion-toolbar>
&nbsp; &nbsp; <ion-title>Second</ion-title>
&nbsp; </ion-toolbar>
</ion-header>

<ion-content padding>
&nbsp; <ion-card>
&nbsp; &nbsp; <ion-card-header>
&nbsp; &nbsp; &nbsp; <ion-card-title>Congratulation!</ion-card-title>
&nbsp; &nbsp; </ion-card-header>

&nbsp; &nbsp; <ion-card-content>
&nbsp; &nbsp; &nbsp; You get price from our sponsor:
&nbsp; &nbsp; &nbsp; <h2>{{price}}</h2>
&nbsp; &nbsp; </ion-card-content>
&nbsp; </ion-card>
</ion-content>

If you plan to send push notification to the group of topic, add this lines inside the platform ready.

this.fcm.subscribeToTopic('people');

To unsubscribe from topic, add this line.

this.fcm.unsubscribeFromTopic('marketing');

4. Run and Test Sending and Receiving Push Notification

Before running this Ionic 4 app, we have to copy the downloaded google-services.json file to the root of the project. Type this command to add the Android platform.

ionic cordova platform add android

Next, copy the google-services.json to the platform/android/ directory.

cp google-services.json platform/android/

Next, run the Ionic 4 App to the Android device by type this command.

ionic cordova run android

After the app running on the device, check the console from the Google Chrome by type this address chrome://inspect then choose the inspect link. You should take to the browser inspector, just change to the console tab.

As you can see above, you can take and write down the FCM token for use by Postman. Next, open the Postman application from your computer. Change the method to POST and add this address [https://fcm.googleapis.com/fcm/send](https://fcm.googleapis.com/fcm/send "https://fcm.googleapis.com/fcm/send"). On the headers, add this key Content-Type with value application/json and Authorization with value key=YOUR_FIREBASE_KEY....

Next, add this JSON data to the RAW body.

{
&nbsp; "notification":{
&nbsp; &nbsp; "title":"Ionic 4 Notification",
&nbsp; &nbsp; "body":"This notification sent from POSTMAN using Firebase HTTP protocol",
&nbsp; &nbsp; "sound":"default",
&nbsp; &nbsp; "click_action":"FCM_PLUGIN_ACTIVITY",
&nbsp; &nbsp; "icon":"fcm_push_icon"
&nbsp; },
&nbsp; "data":{
&nbsp; &nbsp; "landing_page":"second",
&nbsp; &nbsp; "price":"$3,000.00"
&nbsp; },
&nbsp; &nbsp; "to":"eadego-nig0:APA91bEtKx9hv50lmQmfzl-bSDdsZyTQ4RkelInfzxrPcZjJaSgDmok3-WQKV5FBu9hrMrkRrcCmf3arkGSviGltg5CyC2F9x1J2m0W7U8PxJ3Zlh7-_tL6VcFdb76hbaLIdZ-dOK15r",
&nbsp; &nbsp; "priority":"high",
&nbsp; &nbsp; "restricted_package_name":""
}

If you want to send by topics recipients, change the value of to to topics/people. Next, click the send button and you should see this response.

{
&nbsp; &nbsp; "multicast_id": 7712395953543412819,
&nbsp; &nbsp; "success": 1,
&nbsp; &nbsp; "failure": 0,
&nbsp; &nbsp; "canonical_ids": 0,
&nbsp; &nbsp; "results": [
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "message_id": "0:1550632139317442%b73443ccb73443cc"
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; ]
}

And you will see the notification in your Android device background screen.

If you tap on it, it will open the App and redirect to the second page with this view.

That it’s, the example of receiving push notification using Ionic 4 and Firebase Cloud Messaging. You can grab the full source code from our GitHub.

Mastering styled text in Flutter

Mastering styled text in Flutter

In this tutorial we are going to start with an overview of Dart strings and Unicode. Next we’ll move on to styling text for your app, first for entire strings and then for spans within a string.

Introduction

In this tutorial we are going to start with an overview of Dart strings and Unicode. Next we’ll move on to styling text for your app, first for entire strings and then for spans within a string.

Prerequisites

To go through this tutorial you should have the Flutter development environment set up and know how to run an app. I’m using Android Studio with the Flutter 1.1 plugin, which uses Dart 2.1.

Setup

Create a new Flutter app. I’m calling mine flutter_text.

Open main.dart and replace the code with the following:

    import 'package:flutter/material.dart';

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            appBar: AppBar(title: Text('Styling text')),
            body: Container(
              child: Center(
                child: _myWidget(context),
              ),
            ),
          ),
        );
      }
    }

    // modify this widget with the example code below
    Widget _myWidget(BuildContext context) {
      String myString = 'I ❤️ Flutter';
      print(myString);
      return Text(
        myString,
        style: TextStyle(fontSize: 30.0),
      );
    }

Note the _myWidget() function at the end. You can modify or replace it using the examples below. The more you experiment on your own, the more you will learn.

If you are already familiar with concepts like grapheme clusters and Dart strings, you can skip down to the text styling sections below.

Unicode

Coded messages

When I was a kid I liked to write “secret” messages in code, where 1=a, 2=b, 3=c and so on until 26=z. A message using this code might be:

    9   12 9 11 5   6 12 21 20 20 5 18


To make the code even more secret you could shift the numbers, where 1=b, 2=c, 3=d and so on until it wrapped around where 26=a. As long as my friend and I had the same code key, we could decode each other’s messages. The wrong code key, though, would give garbled nonsense.

Computers are similar, except most of the time we don’t want secret messages. We want to make our messages easy to decode, so we agree on a code key, or should I say, a standard. ASCII was an early example of this, where the code key was 97=a, 98=b, 99=c, and so on. That worked fine for English but ASCII only had 128 codes (from 7 bits of data) and that wasn’t enough for all of the characters in other languages. So people made other code keys with more numbers. The problem was that the numbers overlapped and when you used the wrong decoding key you ended up with garbled nonsense.

Unicode to the rescue

Unicode is an international standard that assigns unique code numbers for the characters of every language in the world. The code numbers are called code points. In addition to what we normally think of as characters, there are also code points for control characters (like a new line), diacritical marks (like the accent over an é), and pictures (like 😊). As long as everyone agrees to use this code standard, there are no more fewer garbled messages.

Unicode is just a long list of code points. Saving these code points or sending them is another matter. To help you understand this, take my secret message from above as an example. If I write it as a string of numbers without whitespace and try to send it to you, you get:

    9129115612212020518


This is almost impossible to decode now. Does 912 mean 9, 1, 2 or does it mean 9, 12? It’s the same situation with Unicode. We have to use an agreed upon means to save and send Unicode text, or else it would be very difficult to decode. There are three main ways to do it: UTF-8, UTF-16, and UTF-32. UTF stands for Unicode Transformation Format, and each method of encoding has its advantages and disadvantages.

  • UTF-8 saves each code point using one to four bytes of data.
  • UTF-16 saves each code point as two or four bytes of data. One 16-bit code unit is big enough to uniquely reference a lot of Unicode code points, but not big enough for all of them (emojis, for example). In order to save code points with numbers higher than 16 bits (that is, higher than the number 65,535), UTF-16 uses two 16-bit code units (called surrogate pairs) to map the other code points.
  • UTF-32 saves each code point using four bytes of data. It provides a direct one-to-one mapping of UTF-32 code units to Unicode code points.

When working with UTF-16 code units, you need to be careful not to forget about the other half of a surrogate pair. And even if you are working with UTF-32, you shouldn’t assume that a single code point is the same as what a user perceives to be a character. For example, country flags (like 🇨🇦) are made of two code points. An accented character (like é) can also optionally be made from two code points. In addition to this, there are emoji with skin tone (like 👩🏾, 2 code points) and family emoji (like 👨‍👩‍👧, 5 code points).

So as a programmer, it is better not to think of UTF code units or Unicode code points as characters themselves. That will lead to bugs (for example, when trying to move the cursor one place to the left). Instead, you should think about what Unicode calls a grapheme cluster. These are user-perceived characters. So 🇨🇦, é, 👩🏾, and 👨‍👩‍👧 are each a single grapheme cluster because they each look like a single character even though they are made up of multiple Unicode code points.

Further reading

If you find this interesting or would like a deeper understand of the issues related to Unicode, I encourage you to read the following articles:

  • UTF-8 saves each code point using one to four bytes of data.
  • UTF-16 saves each code point as two or four bytes of data. One 16-bit code unit is big enough to uniquely reference a lot of Unicode code points, but not big enough for all of them (emojis, for example). In order to save code points with numbers higher than 16 bits (that is, higher than the number 65,535), UTF-16 uses two 16-bit code units (called surrogate pairs) to map the other code points.
  • UTF-32 saves each code point using four bytes of data. It provides a direct one-to-one mapping of UTF-32 code units to Unicode code points.
Dart strings

Let’s move on from talking about Unicode in a general way to seeing how Dart uses it.

Code units

In Dart, strings are sequences of UTF-16 code units. That makes string manipulation look deceptively easy because you can get the string value of a code unit by a random integer index:

    String myString = 'Flutter';
    String myChar = myString[0]; // F

But this creates bugs if you split a surrogate pair.

    String myString = '🍎';                    // apple emoji
    List<int> codeUnits = myString.codeUnits;  // [55356, 57166]
    String myChar = myString[0];               // 55356 (half of a surrogate pair)

This will throw an exception if you try to display myChar in a Text widget.

Runes

A better alternative is to work with code points, which are called runes in Dart.

    String myString = '🍎π';

    List<int> codeUnits = myString.codeUnits;    // [55356, 57166, 960]
    int numberOfCodeUnits = myString.length;     // 3
    int firstCodeUnit = myString.codeUnitAt(0);  // 55356

    Runes runes = myString.runes;                // (127822, 960)
    int numberOfCodPoints = runes.length;        // 2
    int firstCodePoint = runes.first;            // 127822

Grapheme clusters

Even runes will fail when you have grapheme clusters composed of multiple code points.

    String myString = '🇨🇦';
    Runes runes = myString.runes;                // (127464, 127462)
    int numberOfCodePoints = runes.length;       // 2
    int firstCodePoint = runes.first;            // 127464
    String halfFlag = String.fromCharCode(firstCodePoint); // 🇨

Displaying the halfFlag string in your app won’t crash it, but users will perceive it as a bug since it only contains one of the two regional indicator symbols used to make the Canadian flag.

Unfortunately, at the time of this writing, there is no support for grapheme clusters in Dart, though there is talk of implementing it. You should still keep them in mind while writing tests and working with strings, though.

Hexadecimal notation

If you are starting with a Unicode hex value, this is how you get a string:

    String s1 = '\u0043';                // C
    String s2 = '\u{43}';                // C
    String s3 = '\u{1F431}';             // 🐱 (cat emoji)
    String s4 = '\u{65}\u{301}\u{20DD}'; //  é⃝ = "e" + accent mark + circle
    int charCode = 0x1F431;              // 🐱 (cat emoji)
    String s5 = String.fromCharCode(charCode);

Substrings

The String documentation (here and here) is pretty good, and you should read it if you haven’t already. I want to review substrings before we go on to text styling, though, since we will be using it later.

To get a substring you do the following:

    String myString = 'I ❤️ Flutter.';
    int startIndex = 5;
    int endIndex = 12;
    String mySubstring = myString.substring(startIndex, endIndex); // Flutter

You can find index numbers with indexOf():

    int startIndex = myString.indexOf('Flutter');

OK, that’s enough background information. Let’s get on to styling text in Flutter.

Text styling with the Text widget

We are going to look first at styling strings in a Text widget. After that we will see how to style substrings within a RichText widget. Both of these widgets use a TextStyle widget to hold the styling information.

Replace _myWidget() with the following code:

    Widget _myWidget(BuildContext context) {
      return Text(
        'Styling text in Flutter',
        style: TextStyle(
          fontSize: 30.0,
        ),
      );
    }

Or, if you would like to compare multiple style settings at once, you can use the following column layout.

    Widget _myWidget(BuildContext context) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            'Styling text in Flutter',
            style: TextStyle(
              fontSize: 8,
            ),
          ),
          Text(
            'Styling text in Flutter',
            style: TextStyle(
              fontSize: 12,
            ),
          ),
          Text(
            'Styling text in Flutter',
            style: TextStyle(
              fontSize: 16,
            ),
          ),
        ],
      );
    }

Note that I am setting the TextStyle using the style property of the Text widget. I will modify the TextStyle options below. Try them out yourself by pressing hot reload between every change. You may want to leave a large font size (like fontSize: 30) for some of the later examples below so that you can see what is happening.

Text size

    TextStyle(
      fontSize: 30.0,
    )

When fontSize is not given, the default size is 14 logical pixels. Logical pixels are independent of a device’s density. That is, the text should appear to be to be basically the same size no matter what the pixel density of a user’s device may be. However, this font size is also multiplied by a textScaleFactor depending on the user’s preferred font size.

If you wish to disable accessibility scaling, you can set it on the Text widget. (I’m very impressed that Flutter has accessibility enabled by default, and I definitely don’t suggest that you disable it without reason. In some rare cases, though, an oversized font might break a layout…in which case it would still probably be better to redesign your layout rather than disable accessibility.)

    // This text will always display at 30.0 logical pixels, no matter
    // what the user's preferred size is.
    Text(
      'Some text',
      textScaleFactor: 1.0, // disables accessibility
      style: TextStyle(
        fontSize: 30.0
      ),
    )

You can also use the theme data to set the text size. See the section on themes below.

Text color

    TextStyle(
      color: Colors.green,
    )

In addition to predefined colors like Colors.green and Colors.red, you can also set shades on a color, like Colors.blue[100] or Colors.blue[700].

Background color

    Widget _myWidget(BuildContext context) {
      Paint paint = Paint();
      paint.color = Colors.green;
      return Text(
        'Styling text in Flutter',
        style: TextStyle(
          background: paint,
          fontSize: 30.0,
        ),
      );
    }

For a Text widget you could also just wrap it in a Container and set the color on the Container.

Bold

    TextStyle(
      fontWeight: FontWeight.bold,
    )

You can set the weight with numbers like FontWeight.w100 where w400 is the same as normal and w700 is the same as bold.

Italic

    TextStyle(
      fontStyle: FontStyle.italic,
    )

The only choices are italic and normal.

Shadow

    TextStyle(
      shadows: [
        Shadow(
          blurRadius: 10.0,
          color: Colors.blue,
          offset: Offset(5.0, 5.0),
        ),
      ],
    )

When setting the shadow you can change the blur radius (bigger means more blurry), color, and offset. You can even set multiple shadows as if there were more than one light source.

    TextStyle(
      shadows: [
        Shadow(
          color: Colors.blue,
          blurRadius: 10.0,
          offset: Offset(5.0, 5.0),
        ),
        Shadow(
          color: Colors.red,
          blurRadius: 10.0,
          offset: Offset(-5.0, 5.0),
        ),
      ],
    )

I’m not sure if more than one shadow is useful or not, but it is interesting.

Underline

    TextStyle(
      decoration: TextDecoration.underline,
      decorationColor: Colors.black,
      decorationStyle: TextDecorationStyle.solid,
    )

The decoration can be underline, lineThrough, or overline. The last line of text in the image above has an overline.

The choices for decorationStyle are solid, double, dashed, dotted, and wavy.

Spacing

    TextStyle(
      letterSpacing: -1.0,
      wordSpacing: 5.0,
    )

In the example image, the six lines on top use letter spacing ranging from -2.0 to 3.0. The six lines on bottom use word spacing ranging from -3.0 to 12.0. A negative value moves the letters or words closer together.

Font

Using a custom font requires a few more steps:

  1. Add a directory called assets to the root of your project.
  2. Copy a font into it. (I downloaded the Dancing Script font from here, unzipped it, and renamed the regular one to dancing_script.ttf.)
  3. In pubspec.yaml register the font:
    flutter:
      fonts:
      - family: DancingScript
        fonts:
        - asset: assets/dancing_script.ttf

  1. Add a directory called assets to the root of your project.
  2. Copy a font into it. (I downloaded the Dancing Script font from here, unzipped it, and renamed the regular one to dancing_script.ttf.)
  3. In pubspec.yaml register the font:
    TextStyle(
      fontFamily: 'DancingScript',
    )

  1. Add a directory called assets to the root of your project.
  2. Copy a font into it. (I downloaded the Dancing Script font from here, unzipped it, and renamed the regular one to dancing_script.ttf.)
  3. In pubspec.yaml register the font:

See this post for more help.

Using themes

Our root widget is a MaterialApp widget, which uses the Material Design theme. Through the BuildContext we have access to its predefined text styles. Instead of creating our own style with TextStyle, you can use a default one like this:

    Text(
      'Styling text in Flutter',
      style: Theme.of(context).textTheme.title,
    )

That was the default style for titles. There are many more defaults for other types of text. Check them out:

If a style is not specified, Text uses the DefaultTextStyle. You can use it yourself like this:

    Text(
      'default',
      style: DefaultTextStyle.of(context).style,
    )

DefaultTextStyle gets its style from the build context.

See the documentation for more about using themes.

Text styling with the RichText widget

The final thing I want to teach you is how to style part of a text string. With a Text widget the whole string has the same style. A RichText widget, though, allows us to add TextSpans that include different styles.

Basic example

Replace _myWidget() with the following code:

    Widget _myWidget(BuildContext context) {
      return RichText(
        text: TextSpan(
          // set the default style for the children TextSpans
          style: Theme.of(context).textTheme.body1.copyWith(fontSize: 30),
          children: [
            TextSpan(
                text: 'Styling ',
            ),
            TextSpan(
              text: 'text',
              style: TextStyle(
                color: Colors.blue
              )
            ),
            TextSpan(
                text: ' in Flutter',
            ),
          ]
        )
      );
    }

Note: An alternate way to make text with styled spans is to use the Text.rich() constructor, which has the same default style as the Text widget.
RichText takes a TextSpan tree. Every very TextSpan takes more TextSpan children, which inherit the style of their parent. To make the word “text” blue, I had to divide the string into three TextSpans. I used a color for the style, but I could have just as easily used any of the other styles that we have already looked at. Try adding a few more styles yourself.

Styling programmatically

In a real application we would probably have a longer string. For example, let’s highlight every occurrence of “text” in the following string:

To do that we have to look at the string and find the indexes of the text that we want to style. Then we use substring to cut the string up and put it in a list of TextSpans.

Replace _myWidget() with the following code:

    Widget _myWidget(BuildContext context) {

      final String myString =
          'Styling text in Flutter Styling text in Flutter '
          'Styling text in Flutter Styling text in Flutter '
          'Styling text in Flutter Styling text in Flutter '
          'Styling text in Flutter Styling text in Flutter '
          'Styling text in Flutter Styling text in Flutter ';

      final wordToStyle = 'text';
      final style = TextStyle(color: Colors.blue);
      final spans = _getSpans(myString, wordToStyle, style);

      return RichText(
        text: TextSpan(
          style: Theme.of(context).textTheme.body1.copyWith(fontSize: 30),
          children: spans,
        ),
      );
    }

    List<TextSpan> _getSpans(String text, String matchWord, TextStyle style) {

      List<TextSpan> spans = [];
      int spanBoundary = 0;

      do {

        // look for the next match
        final startIndex = text.indexOf(matchWord, spanBoundary);

        // if no more matches then add the rest of the string without style
        if (startIndex == -1) {
          spans.add(TextSpan(text: text.substring(spanBoundary)));
          return spans;
        }

        // add any unstyled text before the next match
        if (startIndex > spanBoundary) {
          spans.add(TextSpan(text: text.substring(spanBoundary, startIndex)));
        }

        // style the matched text
        final endIndex = startIndex + matchWord.length;
        final spanText = text.substring(startIndex, endIndex);
        spans.add(TextSpan(text: spanText, style: style));

        // mark the boundary to start the next search from
        spanBoundary = endIndex;

      // continue until there are no more matches
      } while (spanBoundary < text.length);

      return spans;
    }

Experiment with changing the search word and style.

In this example we searched for plain text, but you can also do pattern matching using regular expressions.

Clickable spans

You can make a span clickable by adding a TapGestureRecognizer:

    TextSpan(
      text: spanText,
      style: style,
      recognizer: TapGestureRecognizer()
        ..onTap = () {
          // do something
        },
    )

This would allow you to open a URL, for example, if used along with the url_launcher plugin.

Final notes

Here are a few more related concepts that I didn’t have time or space to cover:

  • UTF-8 saves each code point using one to four bytes of data.
  • UTF-16 saves each code point as two or four bytes of data. One 16-bit code unit is big enough to uniquely reference a lot of Unicode code points, but not big enough for all of them (emojis, for example). In order to save code points with numbers higher than 16 bits (that is, higher than the number 65,535), UTF-16 uses two 16-bit code units (called surrogate pairs) to map the other code points.
  • UTF-32 saves each code point using four bytes of data. It provides a direct one-to-one mapping of UTF-32 code units to Unicode code points.
Conclusion

Text seems like it should be so simple, but it really isn’t. Language is messy and dealing with it as a programmer can be difficult. Much progress has been made in recent years, though. Unicode has solved a lot of problems. Dart and Flutter also give us a lot of tools to manipulate and style text. I expect to see these tools improve even more in the future.

The source code for this project is available on GitHub.

By the way, in case you were curious but lazy, my secret message was “I like Flutter”.