Creating a Flutter App for Web

I’ve covered this topic previously in my post Create, Build and Publish a Flutter Web App but things have changed a little now as web support has been merged, making it easier to build a single application that runs on iOS, Android and Web. To find out more you can check out the Flutter docs for web and Building a web application with Flutter. In this post, I’m going to cover my experience and talk a little about the debugging experience in Visual Studio Code.

With the release of Flutter 1.9 I took a read through the announcement from the Google Developer Days in China and I was initially a little thrown because it indicated that Flutter’s web support has been integrated into the main Flutter repository. I assumed this meant that if I upgraded to 1.9 I would be able to immediately create a Flutter app that targets the mobile and web platforms.

Switching Channel

After upgrading to 1.9 and trying to create a new Flutter project, I quickly realised that I had misunderstood the announcement and that I still needed to switch to a different channel in order to get the integrated experience for Flutter web. So I ran the following command

flutter channel master

After switching to the master channel, I ran:

flutter doctor
flutter upgrade
flutter config --enable-web

New Flutter Project With Web

Once you’ve run these methods, the next thing to do is to create a new Flutter project in Visual Studio code. Press Ctrl+Shift+P to bring up the Command Palette, type Flutter and click Flutter: New Project. After your project is created you’ll notice the addition of a web folder.

Launching Flutter for Web

When you go to launch your Flutter app from within Visual Studio Code you’ll need to decide whether to launch on iOS or Andoird, or the newly supported web. In the lower right corner of Visual Studio Code you can see the current debugging target – in this case it’s already set to use Chrome.

If you tap the debugging target you’ll be prompted to pick an alternative debugging target.

After setting the debugging target, if you press F5 your Flutter app will launch on the appropriate device or emulator. Note that since web support is in preview, there’s no step-through debugging support; you can set breakpoints etc, you just can’t step through the code and inspect variables.

Summary

This was just a short post to draw your attention to the need to switch to the master branch in order to try out the Flutter for web support.

Create, Build and Publish a Flutter Web App

Using Flutter for building iOS and Android applications brings with it some advantages over other cross platform solutions such as Xamarin.Forms or React but one of the more interesting developments to keep an eye on is the support for running Flutter apps on the web. In this post we’re going to create a basic Flutter app and show it running locally. We’re then going to publish it out to Azure blob storage (and accessed via the Azure CDN) to demonstrate that a Flutter web app runs purely in the browser and can be hosted on a static endpoint (i.e. no server side code!).

Installation and Setup

Currently support for Flutter on web requires the webdev package but before installing the package it’s important to make sure that you’ve upgraded the Flutter SDK to the latest version. Run the upgrade command either from a command prompt, or from the terminal console within Visual Studio Code.

flutter upgrade

Next, we need to activate the webdev package using the following command

flutter packages pub global activate webdev

Note there are a couple of things that I ran into. I’m running this all on Windows 10, and I was trying to activate the webdev from within Visual Studio Code. I had opened a new window, which meant I didn’t have a Flutter project open. When I ran the above command I saw an error because apparently there was no pubsec.yaml file.

PS C:\> flutter packages pub global activate webdev
 Error: No pubspec.yaml file found.
 This command should be run from the root of your Flutter project.
 Do not run this command from the root of your git clone of Flutter.

After hunting around a bit I found that you can omit “packages” from the command in order for the webdev package to be retrieved and activated (only required to be done once thanks to the global parameter):

flutter pub global activate webdev

This command might take a minute or so to run. On completion, I saw an interesting notice regarding the cache folder for packages:

Installed executable webdev.
 Warning: Pub installs executables into C:\Source\tools\flutter\.pub-cache\bin, which is not on your path.
 You can fix that by adding that directory to your system's "Path" environment variable.
 A web search for "configure windows path" will show you how.
 Activated webdev 2.5.0.

Currently, the only folder that I’ve included in the Path environment variable is the bin folder for where I’d extracted the Flutter SDK (in my case c:\source\tools\flutter\bin). This was the first time I’d seen this warning and it made me wonder what other folders I needed to include. Following the direction of this post, coupled with the above warning I added the following paths to the Path environment variable.

[flutter folder]\bin
[flutter folder]\.pub-cache\bin
[flutter folder]\bin\cache\dart-sdk\bin
%APPDATA%\Pub\Cache\bin

Note: Make sure you restart any command prompt, terminal window and Visual Studio Code after saving the changes to the Path environment variable in order for it to take effect.

At this point you should be good to go ahead and start creating your new Flutter for web project.

Flutter: New Web Project

From the command palette in Visual Studio Code you can select Flutter: New Web Project. This will go ahead and create your new project with a folder structure that will seem familiar if you’ve been building Flutter apps already. What is different is that after creating the project, it opens index.html in the code editor – you can close this as you probably don’t need to mess with this page initially.

You’ll also notice that there are two main.dart files, one in the web folder, and one in the lib folder. If you inspect these individually you’ll notice that the one in the web folder is an entrypoint (you can see main.dart.js being invoked from index.html which is located in the same folder) which initializes the web platform before calling the main() function located in main.dart in the lib folder. Inside the main.dart in the lib folder you’ll see the layout of a very basic “Hello World” Flutter app.

Running Flutter for Web

Of course, the first thing you’ll want to do is to run the Flutter app, which can be done easily in Visual Studio Code by pressing F5. The first time you attempt to run the project you may be prompted to Select Environment. Select Dart & Flutter to proceed.

Unfortunately this then greets me with a dialog saying that there are build errors:

NOTE: If you get build errors on a newly created Flutter web project. Run flutter upgrade from the terminal within Visual Studio Code, or from the command prompt.

After upgrading my Flutter web project, pressing F5 and selecting the environment, builds and runs the project. Your default browser, Chrome in my case, will be launched in order to display your Flutter web project. The first run does take a while, so don’t close Chrome if it just seems to be blocked loading the project the first time.

Publishing a Flutter Web App

It’s all great being able to run the Flutter web project from within Visual Studio Code (or via the command line using webdev serve). However, at some point you’ll want to think about publishing the project to the web. The great thing about a Flutter web app is that the compiler generates JavaScript that runs within the browser to power your app. There’s no server side component required in order to serve your app.

Ok, but where is this JavaScript and how do I know what to copy to my web server? When you get around to publishing your web app, instead of running webdev serve you just need to use the build parameter:

webdev build

This command will generate a build folder, which will include index.html and main.dart.js, along with any other files that your Flutter web app will require in order to run. All you need to do is to copy the contents of the folder to the appropriate folder on your web server.

In my case I didn’t want to spin up a web server, or even a new endpoint in Azure, just for my simple Flutter web app. Instead I decided to copy the necessary files up to a folder in an existing Blob storage account (in fact it’s the very same storage account that serves the images for this blog). In front of this storage account I use an Azure CDN endpoint in order to improve performance and caching of the content. The net result is that any file that’s retrieved from https://blogimages.builttoroam.com is served from the CDN cache, if it exists. If it isn’t already in the CDN cache, it’ll be pulled from https://nicksblog.blob.core.windows.net, which is the underlying blob storage endpoint, added to the CDN cache and then served up. The point I’m making is that the following Flutter web app (it’s not an image – you can click on it to launch it in a separate tab) is being served from a static service, rather than an actual web server.

Summary

Whilst Flutter for web is still in preview, the simplicity of using the Flutter layout engine for building apps for the web will be a game changed for those of us who have hated the prospect of developing using CSS and JS. Hopefully as it gets closer to a final release there will be an ability to have a single project that’s able to build for iOS, Android and Web without having to have separate projects.

Just out of interest, here are a couple of other articles worth reading on Flutter for the web:


NDC – Sydney – October 14-18

Workshop: Building Cross-Platform Apps With Flutter
Presenters: Pooja Bhaumik and myself

Register for NDC Sydney and come learn how to build amazing Flutter apps!!


How to Create a Flutter Widget Using a RenderObject

There is plenty of documentation on how to build a Flutter application and more importantly the significance of widgets. Most articles you’ll read about widgets talk about what a StatelessWidget is and what a StatefulWidget is and how you can inherit from these to create your own widgets. However, what happens when you want to create a widget that perhaps does some custom painting, or can’t easily be represented by a combination of existing widgets?

One option is that you add your custom painting to the widget hierarchy. For example in this StackOverflow posting the author, Collin Jackson, creates a class called ProgressPainter that inherits from CustomPainter. An instance of the ProgressPainter is added to the widget hierarchy in order to paint directly to the canvas.

An alternative is to create a class that inherits from RenderObject in order to encapsulate the painting as part of the rendering of a widget. This type of encapsulation is used by the built in Flutter widgets. The example we’ll walk through in a second actually mirrors the code for the Opacity widget, a built in Flutter widget that allows you to control the opacity of child widgets.

What’s a Widget, Element and RenderObject

Before we get into the example, it’s worth understanding the relationship between a widget and its corresponding RenderObject. There are a couple of other posts that are worth a quick read that cover the difference between a Widget, an Element and a RenderObject:

As you start to get familiar with Flutter, you’ll find yourself defining what you want to appear on the screen using a mixture of widgets. Each time you use a widget you’re setting properties on it or nesting other widgets within it. You use widgets to describe what the visual hierarchy should look like at a point in time. You can think of widgets holding configuration information about the visual hierarchy, or being a template for the visual hierarchy.

When your application runs and an instance of a widget is created it’s associated with an Element. Where widgets are immutable and may be recreated based on changing state of the application, Elements mutate based on the widget that they’re associated with. Elements combine in a tree structure to define the current layout of the application.

You’d think that if an Element defines the current layout of the application it would be what draws, or renders, each widget. This role actually resides with the RenderObject which is attached to a RenderObjectElement, a sub-class of Element (in contrast to a ComponentElement which is predominantly responsible for composition of other Elements).

Element and RenderObject By Example

I’m sure at this point this is all sounding very theoretical, so let’s walk through a basic example. After creating a new Flutter project, simple_widgets, I’ve stripped back all the default source code to a minimum app that simply displays Hello World! in the Center of the screen. In the following code we can see that the structure of the app uses a Center widget with a Text widget nested via the child property.

void main() => runApp(
      Center(
        child: Text(
          'Hello World!',
          textDirection: TextDirection.ltr,
        ),
      ),
    );

In VS Code if you press Ctrl+Alt+D the Dart DevTools will be displayed attached to the currently running Flutter app. As you can see from the left tree on the following image, our App is indeed made up a Center with Text nested within it. However, if we look on the right side we can see that there’s actually an additional node on the tree, RichText. The RichText node is actually a nested widget that is returned by the build method of the Text widget and actually does the heavy lifting for the Text widget.

What we’re really seeing in the Dart DevTools are actually the elements that have been created and correspond to what’s being displayed on the screen. To see this clearly you can click the “Debug Paint” button (to the right of the clock button in the toolbar of the Dart DevTools) and then select the RichText node in the right tree. In the running app you’ll see markers similar to the following image identifying the element that’s being displayed on the screen.

If a Widget is the template, or cookie-cutter, and an Element is the instantiation of a widget, the question is where does all the work get done to display content on the screen. This work happens within a RenderObject, or in most cases a derivative of the RenderObject class. In the case of the RichText widget, it creates a RenderParagraph which is solely responsible for rendering out the text associated with the RichText widget.

As you can see from the earlier image taken from the Dev DartTools, the RenderParagraph includes properties such as constraints and size, that are common to all RenderObjects, as well as textStart, textDirection etc. The implementation of the RenderParagraph is too complex to go into here but needless to say it’s responsible for rendering the text associated with the RichText widget to the screen.

Creating a Tint Widget

The purpose of the widget is to overlay a tint across the content specified via the child property of the widget. The code for this widget isn’t too dissimilar to the built in Opacity widget. There going to be two classes involved:

  • Tint – this is a widget that a developer can use in their widget hierarchy in order to provide a coloured overlay, specified by the color property.
  • RenderTint – this is the descendent of RenderObject and will be responsible for painting the tint overlay.

Tint Widget

The Tint widget itself is relatively straightforward, mainly because we inherit from SingleChildRenderObjectWidget that does most of the heavy lifting for us. The important aspects of the class are that it accepts a Color as a required parameter for its constructor and that it overrides both the createRenderObject and the updateRenderObject. In the createRenderObject it returns an new instance of the RenderTint class.

class Tint extends SingleChildRenderObjectWidget {
  const Tint({
    Key key,
    @required this.color,
    Widget child,
  })  : assert(color != null),
        super(key: key, child: child);

  final Color color;

  @override
  RenderTint createRenderObject(BuildContext context) {
    return RenderTint(
      color: color,
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderTint renderObject) {
    renderObject
      ..color = color;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ColorProperty('color', color));
  }
}

One thing you’ll notice in the code for the Tint widget is that there’s no code relating to the creation, mounting or unmounting of any element. This is because this is all handled by the SingleChildRenderObjectWidget which overrides the createElement method to return a new instance of the SingleChildRenderObjectElement class.

As a side note, one of the amazing things about Flutter is that as you’re developing your app or creating a widget, you can always drill into the various classes that make up the built-in widgets. In VS Code, pressing F12, when you have a class name selected or the cursor positioned within the class name, will take you to the definition of that class. The Flutter code is extremely well documented and has been split out into various smaller classes to make it easier to identify the purpose of each class.

RenderTint class

Where the purpose of the Tint widget is to allow developers to apply a Tint in their widget hierarchy, the responsibility of the RenderTint class it to draw the tint. The RenderTint is a descendent of the RenderObject. However, rather than implementing all the requirements of a RenderObject, such as calculating size etc, the RenderTint inherits from the RenderProxyBox which handles nearly everything related to layout and positioning.

At the beginning of the class there is some boilerplate code that includes the constructor, that accepts a Color, and the property color that allows the tint colour to be updated. It’s important to note that the setter of the color property does call markNeedPaint, which is important, otherwise the paint method won’t get invoked again.

class RenderTint extends RenderProxyBox {
  RenderTint({
    Color color = Colors.transparent,
    RenderBox child,
  })  : assert(color != null),
        _color = color,
        super(child);

  Color get color => _color;
  Color _color;
  set color(Color color) {
    assert(color != null);
    if (_color == color) return;
    _color = color;
    markNeedsPaint();
    markNeedsSemanticsUpdate();
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      context.paintChild(child, offset);
    }
    context.canvas.drawColor(color, BlendMode.srcOver);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ColorProperty('color', color));
  }
}

The paint method is where the actual drawing takes place. In this case it uses the paintChild method on the context to draw any child widgets and then it uses drawColor to apply a fill colour over the top of the canvas.

Tint in Action

That’s pretty much it for defining a simple widget that is responsible for rendering its own content, rather than just composing other widgets. All we need to do now is make use of it.

void main() => runApp(
      Center(
        child: Tint(
          color: Color.fromARGB(40, 255, 0, 0),
          child: Text(
            'Hello World!',
            textDirection: TextDirection.ltr,
          ),
        ),
      ),
    );

And of course, the final output

Hopefully in this post you’ve got an appreciation for the differences between a Widget, Element and RenderObject. I’d highly recommend spending some time walking through some of the Flutter code and understanding how the classes fit together and how you can build better widgets by understanding the relationship these classes have.

How to Get Started with Flutter

This post will cover the basics of how to get started with Flutter. It’ll provide you with an overview of your first Flutter app and will give you some pointers on how to get familiar with Flutter, Dart, the tools and ecosystem around building apps using the Flutter SDK. Enough about what you’ll take out of this post, let’s get into it by starting at the beginning.

What is Flutter?

According to the technical overview provide that’s part of the documentation at Flutter.dev, Flutter is a “mobile app SDK for building high-performance, high-fidelity, apps for iOS and Android, from a single codebase”. However, there has been a lot of discussion about Flutter being used for Web and embedded as a solution for desktop apps on MacOS, Windows and Linux.

Before we get too much further into what Flutter is, let’s back up a bit and try to understand why Flutter is needed. A number of years ago it became evident that for a mobile app to be successful it needed to be available on at least iOS and Android. The sheer cost and difficulty of keeping two entirely separate codebases up to date meant that most companies look to some form of cross platform solution. Unfortunately the cross platform, until recently has been a bit hit and miss. On one end you had solutions such as PhoneGap / Cordova that provided a native shell around a web app – these apps typically suffered from poor usability, were often slow and felt like they were a mobile website. Then you had solutions like Xamarin that offered native-like performance but without the ability to share the user interface layer. More recently Xamarin.Forms and ReactNative both offer an abstraction for the user interface that relies heavily on the native platform controls. The abstraction layer is often a point of contention amongst developers who often find themselves spending considerable time adapting the platform controls to get consistency in their apps across the different platforms and devices. In addition, the abstraction layer can become a bottleneck resulting in performance and at times stability issues.

What’s interesting about Flutter is that it takes ownership of the entire page and is solely responsible for rendering each widget that makes up the app using Skia. This approach frees developers up from a lot of the platform idiosyncrasies whilst still giving them the a uniform developer experience for defining the user interface, navigation and behaviour of the app in a consistent manner using Dart.

Before we discuss Dart, it’s worth pointing out a couple of resources that help understand the background of Flutter:

What is Dart?

Flutter apps are written almost entirely in Dart, which is a modern programming language with clear similarities to other object-oriented languages such as Java, C#, JavaScript, Objective-C, Swift. According to the official language website dart.dev, Dart is “a client-optimised language for fast apps on any platform” but this doesn’t specifically address why we need yet another programming language.

An interesting article, entitled Why Flutter Uses Dart, from back in 2017 provides a number of points as to why Flutter uses Dart:

  • Dart supports AOT allowing it to optimise down to native code in order to perform well in production.
  • Dart supports JIT, allowing a fast developer cycle, namely hot reload.
  • Dart supports the declaration of layout for Flutter apps without the need of a separate markup language (the article Making Dart a Better Language for UI delves into this in more detail).
  • Dart borrows many existing static and dynamic language features from other languages that developers are already familiar with.

As you get into developing Flutter apps it’s worth spending a bit of time exploring the Dart programming language – there are a number of features that have been added to make it easier to declare the user interface for Flutter apps.

To get familiar with Dart, head over to DartPad where you can experiment with different language features. On the left side you can write out some Dart code; hit the Run button; and see the output displayed in the right pane.

DartPad for Experimenting and Learning Dart

For those moving to Flutter/Dart from C#, Adam Pedley put together a post on Moving From C# to Dart: Quick Start. There’s also a C# to Dart transpiler Windows app available in the Microsoft Store. For Java developers there’s a Codelab entitled Intro to Dart for Java Developers. However, having gone through the codelab I would encourage anyone wanting to learn more about Dart to give it a go, regardless of what language/technology you’re coming from.

Get Started with Flutter – Your First App

At this point I’m going to jump right in and create an app. I’ll walk you through the process of creating and running an app, highlighting some of the tools and steps you should be aware of. I’ll be doing on a Windows PC using Visual Studio Code (VS Code) but the general process is the same if you’re on a different platform, or using a different IDE.

Talking of IDEs, the Flutter SDK works well with both Android Studio and Visual Studio Code. Rather than step you through getting the tools setup, head over to the installation instructions, pick your platform and work your way through the steps to download and install both your IDE (Android Studio or VS Code) and the Flutter SDK.

Flutter: New Project

Let’s get started with a new Flutter project – I’m going to walk through using VS Code but the process should be similar if you’re working with Android Studio. We’ll get started with Flutter using the VS Code Command Palette (Use Ctrl+Shift+P or from the View menu select Command Palette). Assuming you’ve already got the Flutter and Dart extensions installed (go here if you haven’t set these up) if you type ‘flutter’ you’ll see a list of commands that you can invoke that relate to Flutter.

Flutter Commands in the Command Palette

Select “Flutter:New Project” either using the mouse or since it’s the first item you can just press Enter. Next you’ll be prompted to give you app/project a name. Flutter requires it to be in lowercase and use underscore to separate words (i.e. no spaces). In this case we’re just going to be walking through the default project that the Flutter SDK creates which is a simple counter, so we’ll call our project flutter_counter.

Naming Your Flutter Project

After naming your Flutter project you’ll be prompted for a location on your computer where you want the source code to be located. Once you’ve specified a folder, VS Code will go ahead and create your Flutter project. You’ll see the project structure, including files and folders appear in the Explorer on the left, and you’ll see a progress notification appear in the bottom right corner.

Creating Your Flutter Project

Once your project has been created the notification in the bottom right corner of the screen will update to indicate that “Your Flutter project is ready!”

Your Flutter Project is Ready!

A couple of things to note here:

  • The notification also includes instructions on how to run your Flutter app – You can press F5 to start running.
  • Behind the notification you can see in the Output window that there are some additional instructions. It’s worth scrolling back up through the output and having a read. For example, and we’ll come back to this later, the actual command executed to create the Flutter project was “flutter create –ios-language objc –android-language java” which specifies the use of Objective-C and Java for any platform specific code.
  • At the end of the create process it also checked to see whether all the Flutter tooling is correctly setup. In the above screenshot you can just see the last of these checks where it indicates that there is no connected device – we’ll resolve this in a second when we launch the Android emulator. These checks are useful for diagnosing if anything is wrong with your setup and you can run Flutter Doctor for more information/assistance.

Show Me It Running

In VS Code you can press F5 to run the Flutter app. As I mentioned in the previous section, currently I don’t have any devices attached. This means that I’ll see the following prompt appear at the top of the screen. If you have a connected physical device you’ll be able to select

that device at this point.

Connect a Device or Emulator

Picking one of the existing emulator images will launch that emulator and will subsequently build and deploy the flutter_counter app to the emulator. Note that this process can take a few minutes the first time you do it, so be patient and keep an eye on both the Debug Console window and the notifications – these will update to let you know what VS Code is doing. Once your application is deployed and running you’ll see a mini toolbar appear at the top of the VS Code window.

Debugging Toolbar in VS Code

The toolbar allows you to perform typical debugging actions such as pausing execution, step over, step into and step out. It also allows you to trigger a hot reload (we’ll come to this shortly), restart the app and stop the debugging session. In addition to the toolbar you might also see a notification to Open DevTools – don’t worry if you miss this, or any, notification as you can access them again from the status bar at the bottom of the VS Code window by clicking on the bell icon on the far right side.

You can click on the Open DevTools button in the notification to bring up the Dart DevTools. At any stage in the future if you want to bring up the Dart DevTools you can just type “devtools” into the Command Palette to find the appropriate command. The Dart DevTools are incredibly useful as they give you an immediate view of all the widgets that make up your app at any given point in time. You can click on widgets in the tree on the left and see various properties of that widget in the right pane.

Widget Hierarchy Dart DevTools

There are a host of different features of the Dart DevTools that you should explore and will assist you diagnosing and improving your app. Switching back to VS Code, if you look at the editor window you’ll see the code for the flutter_counter app. As this is a very basic Flutter app, all the Dart code is in a single file. However, you’ll want to make sure you establish the structure of your project so that as your application grows it’s easy to navigate the file/folder structure.

VS Code Editor for Dart Code

A couple of things you’ll notice about the editor window:

  • The code is coloured to highlight Dart keywords, class and property names
  • There are vertical lines, making it easy to identify where code blocks start and end
  • Code is nicely indented to again make it easy to identify code blocks

As you start to write your application, you’ll want to make sure you keep your code nicely formatted. The Flutter extension for VS Code comes with a great code formatter which you can invoke either from the menu/context menu or using the Shift+Alt+F keyboard shortcut.

Breakpoints

As with most modern programming languages, Dart/Flutter offers a high fidelity debugging experience. By this I mean that you can set breakpoints where the code execution will be paused, allowing you to inspect variables and the call stack, before stepping forward line by line.

To set a breakpoint in VS Code you simply need to click in the empty space immediately to the left of the line number in the editor window. Alternatively put the cursor on the line where you want to set a breakpoint and press F9 (or set a breakpoint via the Debug menu). In each case a red dot will appear to the left of the line number indicating that a breakpoint has been set. The following image shows the state when a breakpoint has been hit.

Breakpoint in VS Code

The main editor window highlights the line that execution has been paused at (i.e. just prior to that line being executed). At this point you can use the left windows to inspect local variables and observe the call stack. You can then use familiar debugging actions such as Step Over (F10), Step Into (F11) and Step Out (Shift+F11) to progress through the code.

Hot Reload

One of the big features that everyone loves to talk about is the ability to make changes and for them to be reflected immediately in the running app. Coming from a Windows/.NET background the big fuss around hot reload seems to be blown out of proportion – we’ve had Edit-and-Continue for as long as I can recall. Admittedly this hasn’t been available, until recently, for Xamarin.Forms developers.

Anyhow, to give you an idea of what’s possible, lets change the text that displays the counter value in our flutter_counter app. Here’s the current code.

Text(
  '$_counter',
  style: Theme.of(context).textTheme.display1,
),

With the flutter_counter app running, let’s change the code to the following:

Text(
  'Current count is $_counter',
  style: Theme.of(context).textTheme.display1,
),

When you save the file, you’ll see that the app automatically picks up the changes and updates the running app. On the left is the original app; on the right is the app with the updated Text – updated without having to stop and restart the app. Note that the count, which is part of the state of the app, remains the same.

How Should You Get Started With Flutter?

So far we’ve covered an introduction to Flutter and Dart, and walked through some of the tools as part of creating a simple counter app. The question is where should you go next to learn more about Flutter?

The best way to build skills and familiar is just to get started with Flutter – get the tools and start building an app. Of course, you’ll want some assistance along the way, so here’s just a few resources that you may want to take advantage of.

Online Courses on Flutter

If you want to follow more structured learning, there are a bunch of online courses that cover the basics and will help you get started with Flutter. The following is a selection of some of the more popular courses.

Udemy
Getting Started With Flutter 1.0
Learn Flutter & Dart to Build iOS & Android Apps
The Complete Flutter App Development Course for Android, iOS

Pluralsight
Flutter: Getting Started

Udacity
Build Native Mobile Apps with Flutter

Flutter Crash Course
4 modules: The Basics, Flutter for Junior Devs, The Tourism & Co.App, Pro Flutter Essentials

App Brewery
The Complete Flutter Development Bootcamp Using Dart

Flutter Learn
https://flutterlearn.com

Raywenderlick
Getting Started with Flutter

Labs and Challenges

For those who prefer to explore and learn through trying, there are a bunch of coding labs and challenges that you can take on.

Google
Flutter CodeLabs

Coding Challenges
Dart Codewars
Uplabs design challenges
Try reproducing any of the designs on Dribbble

Community Resources

The Flutter community has an amazing set of contributor producing blogs, videos and collecting lists of resources that are useful for Flutter developers at every level.

YouTube / Video
Mtechviral
VoidRealms
Raja Yogan
Google Developers – Flutter
Fireship.io – Flutter

Resources
Awesome flutter – resources list
PoojaB26 – AwesomeFlutterPlaylist

News, Chat and People

Lastly, there are some great sites, mailing list, chats and people that you can participate or follow in order to ask for help, or just keep up with what’s going on in the Flutter-verse.

News
Flutter weekly
Medium – Flutter

Chat
Gitter – Flutter
MindOrks Slack channel

Twitter
@FlutterWk
@flutterio
@
r_FlutterDev
Tim Sneath @timsneath
Eugenio Marletti @workingkills
Seth Ladd @sethladd
Nilay Yener @nlycskn
Pooja Bhaumik @pblead26
Brett Morgan @DomesticMouse
Thomas Burkhart @ThomasBurkhartB

Get Started With Flutter

As you can see the world of Flutter is rapidly evolving – there are a ton of great resources produced by Google and the community alike. Now’s the time to get familiar with Flutter and start building amazing cross-platform applications.

Navigate Flutter Apps with Routes

One of the most important aspects of an app is the flow or journey that the user takes through the app. Apps are often described in terms of pages, or screens, and navigating between them. In this post I’m going to cover dividing your application into routes, and how to work with the Flutter navigation system.

Whilst a Flutter app is constructed out of widgets, there still needs to be a mechanism for the app to respond to user interaction. For example, tapping on an row in a list of items might display the details of that item. Subsequently tapping on the back button should return the user to the list of items. In order to support navigating between different pages, Flutter includes the Navigator class. According to the documentation the Navigator class is:

 A widget that manages a set of child widgets with a stack discipline. 

For someone who’s just got their head around how to layout widgets, this statement makes no sense. In this post we’re going to ignore this definition and cover the basics of how to navigate between pages. I’m also going to ignore the Flutter definition of a Route. For now we’ll assume that a route is roughly equivalent to a page or a screen in your app. As you can imagine, all but very basic apps have multiple pages that the user is able to navigate between. Flutter apps are no different except that you navigate Flutter apps with routes.

Flutter Navigation

Let’s get into navigating between routes. We’ll start with almost the most basic Flutter app you can create. Technically you can create a Flutter app that doesn’t start with the MaterialApp but then you’re left doing a lot of heavy lifting yourself. The following code sets up a basic app that is comprised of two classes that inherit from StatelessWidget. I could have combined these into a single widget by simply setting the value of the home attribute in the MaterialApp to be the new Container widget. However, I’ve created a separate widget, FirstNoRoutePage, to help make it easy to see how the pages of the app are defined.

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

class MyNoRouteNavApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FirstNoRoutePage(),
    );
  }
}

class FirstNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Text('First Page'),
    );
  }
}

Ad-Hoc Routes with Navigator.push()

Now that we have the basic app structure, it’s time to navigate to our first route. We’re going to use the Navigator widget to push a newly created, or ad-hoc, route onto the stack. The following code:

  • Wraps the Text widget in a FlatButton
  • The onPressed calls the push method on the Navigator, passing in a newly constructed MaterialPageRoute.
  • The MaterialPageRoute builder is set to return a new instance of the SecondNoRoutePage widget
class FirstNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'First Page',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => SecondNoRoutePage(),
              ));
        },
      ),
    );
  }
}

class SecondNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Text(
        'Second Page',
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}

We now have an application that has two pages that the user can navigate between. On the first page, clicking on the “First Page” text will push a new route that shows the second page (i.e. the SecondNoRoutePage widget). Pressing the device back button (Android only) will navigate the user back to the first page. It does this by popping the route off the stack maintained by the Navigator.

Go Back with Navigator.pop()

For the purpose of this post I’ve kept the pages simple, showing only elements necessary to show which page the user is on, or to allow the user to perform an action. I’ve chosen not to use a Scaffold, which means no AppBar. This also means no back button shown in the AppBar. On iOS this makes it impossible for the user to navigate to the previous page as there is no dedicated back button, unlike Android. If you use a Scaffold in your widget, you’ll see the AppBar. The AppBar adjusts to include a back button if there’s more than one route on the stack.

Flutter provides built in support for navigating back to the previous route via the AppBar back button or, in the case of Android, the device back button. In addition, the pop method on the Navigator can be used to pop the current route off the stack. This will return the user to the previous route.

class SecondNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'Second Page',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.pop(context);
        },
      ),
    );
  }
}

What the Current Route?

Earlier in the post we created a new route when navigating to the second page of the app. However, what we glossed over is the fact that the first page of the app is also a route. To show this, let’s add some code that adds the name of the current route to both pages. This will highlight where in the Flutter navigation stack the user is currently at.

class FirstNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var route = ModalRoute.of(context).settings.name;
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'First Page - $route',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => SecondNoRoutePage(),
              ));
        },
      ),
    );
  }
}

class SecondNoRoutePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var route = ModalRoute.of(context).settings.name;
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'Second Page - $route',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.pop(context);
        },
      ),
    );
  }
}

What gets displayed on the two pages are ‘First Page – /’ and ‘Second Page – null’. This indicates that the first route is assigned a name of ‘/’, whilst the second route does not have a name (i.e. null value). The first route is created from the home property being set on the MaterialApp. It is assigned a name equal to the defaultRouteName constant from the Navigator class (i.e. ‘/’) to indicate it is the entry point for the application.

As we created the route for the second page, it’s our responsibility to name the route. In the above scenario we’re creating the route at the point where it is required (i.e. when navigating to the second page), and as such we don’t need to give it a name. That is, unless we want to see that the current route is by inspecting it’s name. Let’s update the call to Navigator.push to include a name for the second route.

onPressed: () {
  Navigator.push(
      context,
      MaterialPageRoute(
        builder: (BuildContext context) => SecondNoRoutePage(),
        settings: RouteSettings(name: '/second')
      ));
},

There’s no requirement for the route name to include the ‘/’. However, it is a convention to do so, and there are some scenarios where the ‘/’ has some implied behaviour.

Named Routes in Flutter Navigation

In the previous section we added a name to the ad-hoc route that we created. An alternative to creating routes as they’re required, is to define the routes for the app up front. For example the following code shows three routes that have been defined for an app and can be used for Flutter navigation.

class Routes {
  static const String firstPage = '/';
  static const String secondPage = '/second';
  static const String thirdPage = '/third';
}

class MyNavApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        Routes.firstPage: (BuildContext context) => FirstPage(),
        Routes.secondPage: (BuildContext context) => SecondPage(),
        Routes.thirdPage: (BuildContext context) => ThirdPage(),
      },
    );
  }
}

The Routes class includes the names of each of the three routes. Inside the constructor of the MaterialApp widget the three routes have been declared. Each route is an association between the route name and the builder method that’s used to construct the widget.

Navigate.pushNamed()

When the user clicks the button, the Navigate.pushNamed method is used to navigate to the corresponding route:

onPressed: () {
  Navigator.pushNamed(context, Routes.secondPage);
},

Skipping Over Routes with Navigator.popUntil

Using named routes not only makes it easier to manage the pages in your app, it also means that you can create conditional logic that is dependent on the route name. For example, the Navigator.popUntil allows you to keep popping routes off the stack until the predicate is true. In the following code the predicate looks at the name property of the route to determine whether it is the first page of the app.

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: FlatButton(
        child: Text(
          'Third Page',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () {
          Navigator.popUntil(context, (r) => r.settings.name == Routes.firstPage);
        },
      ),
    );
  }
}

Setting the Initial Route

Initially the first page of the app was defined by setting the home property. This was used to define the initial route for the app. When the app was updated to use a set of predefined routes, the initial route was inferred by looking for the route where the name matches the defaultRouteName (i.e. ‘/’). If there wasn’t a route with that name isn’t found, an error will be raised and the app will fail to start.

It is also possible to set the initialRoute property on the MaterialApp. However, this doesn’t negate the need to set either the home property, or have a route declared with a name of ‘/’. What’s really interesting about the initialRoute property is that it can be used to launch the app on a route that has other routes in the navigation stack. For example in the following code the initialRoute is set to the thirdPage, which is defined as ‘/second/third’. When this app launches, it will launch showing ThirdPage but if the user taps the back button, they will go to SecondPage and then FirstPage if they tap again.

class Routes {
  static const String firstPage = '/';
  static const String secondPage = '/second';
  static const String thirdPage = '/second/third';
}

class MyNavApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: Routes.thirdPage,
      routes: {
        Routes.firstPage: (BuildContext context) => FirstPage(),
        Routes.secondPage: (BuildContext context) => SecondPage(),
        Routes.thirdPage: (BuildContext context) => ThirdPage(),
      },
    );
  }
}

On app startup the initalRoute is split on ‘/’ and then any routes that match the segments are navigated to. In this case the app navigates to FirstPage, then SecondPage and finally ThirdPage.

Intercepting Back Button

The last topic for this post looks at how to intercept the back button. This can be done by including the WillPopScope widget and returning an appropriate value in the onWillPop callback.

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () {
        return new Future.value(true);
      },
      child: Container(
        alignment: Alignment.center,
        child: Text('Third Page'),
      ),
    );
  }
}

Note that the onWillPop expects a Future to be returned, allowing you to return an asynchronous result. In this case the code is simply returning the value of true to allow the current route to be popped.

Summary of Flutter Navigation

This post on Flutter navigation has covered a number of the navigation related methods on the Navigator class. As part of defining how your app looks, you should define all the routes and how the user will navigate between them. If you do this early in the development process you’ll be able to validate the flow of your app as you continue development.

Connecting Flutter to Azure at MS Ignite | The Tour – Sydney

Flutter at MS Ignite | The Tour – Sydney

This year I was lucky enough to be able to present at Microsoft’s Ignite | The Tour, which has just concluded here in Sydney. What amazed me was that I got to present on a fledgling technology recently released by Google, Flutter. Of course, this by itself wouldn’t have been sanctioned at a Microsoft event, so the important part was that I was presenting on connecting Flutter to Azure, and highlighting just how powerful the Azure options are for mobile app developers on any platform.

Using Flutter to develop cloud enabled mobile applications

Flutter is one of the newest cross-platform mobile application development frameworks and brings with it the ability to generate high-fidelity applications that look amazing on every device. This session begins with a very brief overview of Flutter, covering the tools and resources required to get started. The remainder of the session connects a Flutter application to key Azure services such as Identity and App Center. The key takeaway from this session is how Azure accelerates the development of mobile applications irrespective of what technology or framework the app is developed with.

In this post I’m going to walk through the demo portion of the session, which revolved around the creation of a very simple task style application.

Getting Started – New Project

Let’s get started by creating a new project in VS Code (If you haven’t already got the Flutter SDK installed, go to https://flutter.io and follow the Get Started button in top right corner). In VS Code in you press Ctrl+Shift+P and type Flutter you’ll see all the available options.

image

Select “Flutter: New Project” and you’ll be prompted to give your project a name – I was short on inspiration for a name for the project, hence “World’s Best Flutter App”. Note that the name of your project needs to be all lowercase, no spaces or special characters.

image

You’ll need to select a folder to put your new project in, and then VS Code will generate a default application that consists of a basic UI which includes a button that increments a counter on the screen. We’re going to start by removing most of this and replacing it with:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'World\'s Best Flutter App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'World\'s Best Flutter App'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
    );
  }
}

As with all new projects, at this point I’d highly recommend running the application to make sure everything has been correctly created and that you have a running application – there’s nothing worse than investing hours into crafting an amazing solution only to have to debug it for hours because of some issue that happened early in the process. At this point there’s not too much to the application other than the title bar

image

Basic Task List

The first piece of functionality we’re going to add is the ability to enter a tasks and see them appear in a list. To do this we need a text entry (TextField widget) and a list (ListView widget) and we’re going to present them with the text entry above the list in a vertical stack (Column widget). Before adding the widgets I’m going to add a couple of instance variables to the _MyHomePageState class (note that this is the state that goes with the MyHomePage stateful widget):

class _MyHomePageState extends State<MyHomePage> {
  //// Controller for TextField and List of tasks ////
  final TextEditingController eCtrl = new TextEditingController();
  List<String> tasks = [];

Now we can add our widgets to the body property on the Scaffold widget:

return Scaffold(
  appBar: AppBar(title: Text(widget.title)),
  ////  Body of the app is made up of a single column ////
  body: new Column(
    children: <Widget>[
      ////  TextField to capture input ////
      new TextField(
        controller: eCtrl,
        onSubmitted: (text) {
          tasks.add(text);
          eCtrl.clear();
          setState(() {});
        },
      ),

      ////  Expand ListView to remaining space ////
      new Expanded(
        child: new ListView.builder(
          itemCount: tasks.length,
          itemBuilder: (BuildContext ctxt, int index) {
            return Padding(
              padding: EdgeInsets.fromLTRB(10, 10, 5, 5),
              child: Text(tasks[index]),
            );
          },
        ),
      ),
    ],
  ),
);

A couple of things to note here:

  • It appears that I’ve added a couple of trailing commas – this is by design and lets the auto formatter know to position things on new lines. This is really important as Flutter code can get really messy if you don’t keep an eye on the layout of the code
  • Pressing Shift+Alt+F in VS Code will apply the auto formatter and makes the code much more readable – take a look at the following image to see how VS Code presents the above code
image

At this point we have a working app where a user can enter a task and it’s added to a list of tasks that is presented in the lower area of the screen. There are plenty of online tutorials etc that talk about layout and how setState works so I’m not going to cover that here. If you’re unfamiliar with Flutter/Dart code I would suggest at this point taking the time to go and understand what’s going on in the code – we’re going to build on it, so it’s important you understand how things work before we move on.

Visual Studio App Center

Before adding any more functionality to the app, I want to first take a side step to discuss the use of Visual Studio App Center which has been specifically created by Microsoft to support app developers, providing capabilities for building, testing, distributing apps, along with services such as analytics, crash reporting and push notifications. Currently support for Flutter is via a third party plugin which only provides analytics and crash reporting but we can expect this to grow as more developers take on Flutter and put pressure back onto Microsoft to provide official support, similar to what’s already provided for React Native and other cross platform technologies.

For our tasks app we’re going to make use of the analytics and crash reporting, as well as the support for distributing the application via App Center. To do this we first need to go to App Center and register two applications: one for iOS and one for Android. Got to either https://mobile.azure.com or https://appcenter.ms and sign in using your preferred credentials – my preference is to use my Office 365 (ie Azure Active Directory) account as this plays nicely into the way I sign into the rest of Azure and makes things like billing etc really easy. After creating your account, if you locate the apps list, then in the top right corner you should see an Add new button, which gives you the option to Add new app

image

In the Add new app flyout, you need to provide an App name and provide information about the OS and Platform – these are more important if you’re going to use App Center to build the application but is also relevant to the distribution process, so it’s important that you set these correctly (Note: Adam Pedley has a great walk-through of using App Center for build and distribution of Flutter apps). Since the App name needs to be unique within your App Center instance, it makes sense to use some sort of platform identifier as part of the name so it’s easy to identify the iOS and Android apps in the list.

image

After creating the app, you’ll be presented with some platforms specific getting started documentation. Most of this can be ignored as it’s not relevant to our Flutter application. However, you do need to extract the id that’s been assigned to your application. This can be found as part of the code samples eg:

image

After creating the app registrations in App Center, we now need to add in the Flutter plugin into our application and initialize it with the app id. Note that because App Center issues a different app id for each platform, you need to include some conditional platform logic to determine which app id to use. There are three plugins you need to add to your pubspec.yaml file under the dependencies section:

All packages are based on the source code at https://github.com/aloisdeniel/flutter_plugin_appcenter developed by Aloïs Deniel and Damien Aicheh

dependencies:
  flutter:
    sdk: flutter
  appcenter_analytics: ^0.2.1
  appcenter_crashes: ^0.2.1
  appcenter: ^0.2.1

After adding in the packages, we also need to import the relevant types to the top of our main.dart file:

////  Imports for platform info ////
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/foundation.dart' show TargetPlatform;

////  Imports for App Center ////
import 'package:appcenter/appcenter.dart';
import 'package:appcenter_analytics/appcenter_analytics.dart';
import 'package:appcenter_crashes/appcenter_crashes.dart';

Next we need to initialise the App Center plugin but adding code to the _MyHomePageState class

class _MyHomePageState extends State<MyHomePage> {
  ////  Identifier used for initialising App Center ////
  String _appCenterIdentifier = defaultTargetPlatform == TargetPlatform.iOS
      ? "3037d80f-XXXXXXXXXXX-adb968c67880"
      : "f6737675-XXXXXXXXXXX-be38b29b0f89";

And then a call to the AppCenter.start method when the class initialises – we’re overriding the initState method to call this logic when the instance of the _MyHomePageState is first created:

////  Override Initialise State ////
@override
void initState() {
  super.initState();
  initAppCenter();
}

////  Initialise AppCenter ////
void initAppCenter() async {
// Initialise AppCenter to allow for event tracking and crash reporting
  await AppCenter.start(
      _appCenterIdentifier, [AppCenterAnalytics.id, AppCenterCrashes.id]);
}

The last thing we’re going to do is to add a call to the trackEvent method for when an item is added to the tasks list. For this we’re going to modify the onSubmitted callback on the TextField

onSubmitted: (text) {
  ////  Tracking event ////
  AppCenterAnalytics.trackEvent("Adding item", {"List Item": text});

Now when you launch the app, and start adding tasks you’ll see some tracking of app related activity appear within the Analytics tab of App Center:

Note: If you get errors at this point, you may need to stop and restart your application to make sure the newly added packages are correctly built and deployed as part of your application. Hot reload doesn’t always handle this well.

image

You can see information about events raised by the app

image

Including the ability to drill down into the event and see more information, in this case you can see the List item that were added to the task list – note that this is an application specific key-value combination specified as part of the call to trackEvent.

image

 

Build and Release Automation

Before we can start distributing our app via App Center we typically setup a build and release (aka a DevOps) pipeline for our application – at Built to Roam this is one of the first things we encourage all our clients to do as it significantly cuts down on the manual steps involved, resulting in fewer errors and less wasted time. For this, we rely on Azure DevOps, and again a big shout out to Aloïs Deniel who’s created an extension for Azure DevOps – https://marketplace.visualstudio.com/items?itemName=aloisdeniel.flutter

image

After adding this extension to your Azure DevOps tenant, you can easily setup a build and release pipeline – there are a few steps involved in this, so I’ll keep this for a follow up post. The build steps involve tasks for Installing Flutter, Building the app with Flutter, Copying the generated app to the artefacts folder and the Publishing the artefacts. The release steps simply involves uploading the generated app to App Center. You can of course adjust these to add functionality and perhaps target different environments for testing, and of course uploading to the relevant stores directly from your release pipeline.

Update 22/9/2019: There are two other extensions for Azure DevOps for working with Flutter. The first simply makes it easier to install the Flutter SDK. The second is an update to the extension Aloïs created that adds AAB support – if you want to use this extension, make sure you uninstall the original extension Aloïs created because the two conflict and appear as a duplicate.

Once you have your build and release pipelines in place, you should start to see your releases appear in the Distribute / Releases tab in App Center

image

If you open App Center on your iOS or Android device you can install each release for testing.

Azure App Service

Right now we actually have a pretty useless app since all it does is record tasks in memory. When you restart the app, your list of tasks will be gone. Plus there’s no way to have your list of tasks appear on another device etc. We’re not going to complete the entire app in the blog post but what we do want to do is show how easily you can connect a Flutter app to some backend services hosted in Azure.

First up, let’s take a look at the backend service we’re going to connect to. In the demo I used an ASP.NET Core application that was published to an Azure App Service but I could just have easily used a Functions app. The application consistent of a single controller that currently only supports a Get operation to retrieve a list of tasks, which for the moment are hardcoded.

[Route("api/[controller]")]
[ApiController]
public class TasksController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<Task>> Get()
    {
        return new[]
        {
            new Task {Title = "Task 1", Completed = true, },
            new Task {Title = "Task 2", Completed = false,},
            new Task {Title = "Task 3", Completed = true, },
            new Task {Title = "Task 4", Completed = false,},
            new Task {Title = "Task 5", Completed = true, },
        };
    }
}

public class Task
{
    public string Title { get; set; }
    public bool Completed { get; set; }
}

 Swagger

It’s also worth noting that in the Startup.cs of this application I added in the logic to generate Swagger information for the service (You need to add a NuGet reference to Swashbuckle.AspNetCore.Swagger, Swashbuckle.AspNetCore.SwaggerGen and Swashbuckle.AspNetCore.SwaggerUi):

public class Startup
{
    …
    public void ConfigureServices(IServiceCollection services)
    {
        …
        services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "Simple ToDo", Version = "v1" });
            });
        …
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        …
        app.UseSwagger();
        app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Simple ToDo V1");
            });
        …
    }
}

Update 12/11/2019: If you’re using .NET Core 3 preview, you’ll need to upgrade to the preview of Swashbuckle NuGet packages and change the Info class reference to OpenApiInfo. Although at time of writing, there is an issue with the Dart code generator based on the swagger generated by .NET Core 3 preview, so perhaps best to stick with .NET Core 2.2 until v3 is officially released.

This application was published to a new Azure App Service which meant that both the /api/tasks endpoint, as well as the swagger endpoints were publically available. We’ll come back to securing these in a bit. To be able to call the tasks endpoint we could have gone ahead and created a raw http request and then have to worry about decoding the returned json into a series of entities, for which we’d have to write, or generate, the decoding logic. Instead, I went over to SwaggerHub and imported the swagger endpoint:

image

After selecting “Import and Document API” I entered the url for the swagger endpoint on the Azure App Service eg https://simpletodonick.azurewebsites.net/swagger/v1/swagger.json

image

After hitting Import SwaggerHub will convert your json formatter swagger into YAML – we’re not really interested in this piece, since what we’re after is some generated Dart code. However, before we can go ahead and generate the Dart code we first have to configure the generator options for Dart. Click on the Export button in the top right of the SwaggerHub page, followed by Codegen Options.

image

Locate “dart” in the left hand tree, and then set the browserClient property to false – by default the Dart generator creates code suitable for Dart being used for web development. For Flutter you need to set this property to false.

image

Going back to the Export menu, now select “dart” from under the Client SDK option.

image

This will automatically generate and download a package that will include a strongly typed set of classes for accessing the tasks endpoint. I extract this folder and place the contents of the generated zip file into a new sub-folder called “tasks” at the root of the Flutter application.

image

After adding the files into the folder, I then needed to update the pubspec.yaml file for my application to include the package as a dependency, as well as the http package:

dependencies:
  flutter:
    sdk: flutter
  appcenter_analytics: ^0.2.1
  appcenter_crashes: ^0.2.1
  appcenter: ^0.2.1
  http: '>=0.11.1 <0.12.0'
  swagger: 
    path: tasks 

(note that “swagger” correlates to the name of the imported package which is defined with the pubspec.yaml file within the tasks folder)

Update 22/9/2019: There’s an incompatibility between a new Flutter project and the generated Dart code. To fix it you need to update the pubspec.yaml inside the tasks folder to include the same environment value as the pubspec.yaml for your application.

Now to make use of the generated code in our application, I also needed to add a imports statement to the main.dart file.

////  Import for Swagger ////
import 'package:swagger/api.dart';

As we’re going to be using the Task entity described by the Swagger I need to update the list variable from a List<string> to List<Task>. I also added the base url for the app service.

////  Change List to use Task ////
List<Task> tasks = [];

////  Base url for the Simple ToDo service ////
final _baseUrl = 'https://simpletodonick.azurewebsites.net';

At this point I also needed to fix up the existing task list functionality which expected a string, to use a Task entity. This was in two places, firstly when creating a new task I needed to create a new Task:

tasks.add(
  Task()..title = text,
);

And then when displaying the tasks in the list

child: Text(tasks[index].title),

Now with the app back up and running, we can go ahead and call the service in order to retrieve the tasks when the app launches.

void loadData() async {
  ////  Calling App Service ////

  // Setup api client with base url and token returned by auth process
  var client = ApiClient(basePath: _baseUrl);

  var tasksApi = new TasksApi(client);

  var downloadedTasks = await tasksApi.callGet();
  setState(() {
    tasks = downloadedTasks;
  });
}

The loadData method should be called from the initState method, just after calling initAppCenter. Running the app at this point should return a list of tasks on startup and then allow subsequent tasks to be added (of course at the moment those tasks aren’t saved anywhere!)

Authentication with Azure B2C

Currently there is no security applied to the app service, which is clearly not only bad practice, it also means that it is impossible to personalise the task list to the individual user, since everyone will be returned the same list of tasks. The good new is that we can add authentication to our service without writing a single line of code. To do this we’re going to choose to use Azure B2C. However, Azure App Services can also use a variety of different social credential providers to authenticate the user.

For this application I created a new instance of Azure B2C and setup the default B2C_1_signupandin user flow (aka policy). After creating the instance and the user flow, you still need to associate the Azure B2C instance with the app service and vice versa. To do this, start by creating a new application registration within the Azure B2C portal – go to Applications tab and click Add button.

image

In creating the application registration we want to enable both web app/api and native client. You’ll notice that under the Native Client area there is a Custom Redirect URI specified. To do authentication using the flutter_appauth plugin (which I’ll come to in a second) the Custom Redirect URI should be in a similar format ie <reverse domain>://oauthredirect.

After creating the application registration, take note of the Application ID – you’ll need this when we update the Authentication on the App Service that requires a Client Id.

The next thing to do, also in the Azure B2C portal is to navigate to the User flows (policies) tab and then click the Run user flow button.

image

From the Run user flow dialog, copy the link at the top (eg https://worldsbestflutterapp.b2clogin.com/worldsbestflutterapp.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_signupandin)

Now we need to switch across to the App Service in the Azure Portal and click on the Authentication tab. Initially App Service Authentication will be set to Off and there is a warning that anonymous access is permitted.

image

Switch the App Service Authentication to On and under Authentication Providers, click through on the Azure Active Directory settings.

image

Use the Application Id captured when setting up the Azure B2C as the Client ID. Use the link recorded from the Run user flow dialog as the Issuer Url. Click OK to commit the Azure Active Directory settings.

Back in the Authentication / Authorization settings, make sure that the “Action to take when request is not authenticated” dropdown is set to “Log in with Azure Active Directory” – this will enforce sign in if no Authorization token is presented.

image

After hitting Save, if you attempt to access the app service you’ll find that you get a security exception (if sending raw requests), or redirected to the sign in prompt if attempting to load the endpoint in a browser.

 Authentication in Flutter using Flutter_AppAuth

After setting up authentication on the app service, if you attempt to run the application it won’t be able to retrieve the list of tasks as the requests are unauthenticated. In order to create a request that is authenticated (ie supplies a validate Authorization header in this case) we need to get the user to sign into the application. Luckily there is a Flutter plugin, flutter_appauth (source code), that has been created by Built to Roam colleague Michael Bui, that wraps AppAuth iOS and Android libraries.

image

There are a few steps involved in getting our application setup to use flutter_appauth. We’ll start by adding the package reference to the pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_appauth:
  ...

Next, we need to upgrade the Android application to use the AndroidX libraries instead of the older support libraries. To do this we need to open the Android part of the flutter application in Android Studio. Right-click on the android folder in VS Code and select the Open in Android Studio option.

image

When prompted to “Import Project from Gradle” you can accept the defaults and click Ok. The application may take a few minutes to open in Android Studio and you’ll need to allow a bit of extra time for Android Studio to finish processing the application before the Migrate to AndroidX option becomes enabled in the Refactor menu:

image

Side note: The first couple of times I tried this whilst preparing this demo Android Studio kept saying that there was nothing to update. It turns out that there was an update to the Flutter plugin to Android Studio that was waiting to be installed – I did this, which resulted in Android Studio reloading. After reloading the folder structure under Project expanded to show the referenced flutter plugins as well as any external libraries. At this point running Migrate to AndroidX worked.

When Migrate to AndroidX does work for you it’s not immediately obvious that something has changed…. which is because it hasn’t. Instead Android Studio will have opened up a tool window at the bottom of the screen, prompting you to confirm the Refactoring Preview. Click Do Refactor to complete the migration.

image

If you look in the /android/app/build.gradle file you should see that the dependencies list at the end of the file have been updated.

dependencies {
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.0-alpha'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha'
}

At the point of writing this post the migration process references alpha versions. You can update this to reference the stable versions now:

dependencies {
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}

Whilst in the build.gradle file the compiledSdkVersion and targetSdkVersion should be increased to at least 28. Lastly, update the defaultConfig element to include manifestPlaceholder

defaultConfig {
    applicationId "com.example.worldsbestflutterapp"
    minSdkVersion 16
    targetSdkVersion 28
    ...
    manifestPlaceholders = [
            'appAuthRedirectScheme': 'com.builttoroam.simpletodo'
    ]
}

Note that the appAuthRedirectScheme should match the reverse domain part of the Custom Redirect URI property set when setting up Azure B2C earlier.

For iOS open the /ios/Runner/Info.plist file and add the CFBundleURLTypes key/value as follows.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	...
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>com.builttoroam.simpletodo</string>
			</array>
		</dict>
	</array>
</dict>
</plist>

We’re now ready to write the code to authenticate the user within the application. Start by adding an imports to bring in flutter_appauth

////  Import for Flutter_AppAuth ////
import 'package:flutter_appauth/flutter_appauth.dart';

Next, add in some constants that are needed for the authentication process to the beginning of the _MyHomePageState class, as well as an instance of the FlutterAppAuth class.

class _MyHomePageState extends State<MyHomePage> {
  ////  Attributes required for authenticating with Azure B2C ////
  final _clientId = '7c57c3a6-bedb-41d6-9dcd-318224013a26';
  final _redirectUrl = 'com.builttoroam.simpletodo://oauthredirect';
  final _discoveryUrl =
      'https://worldsbestflutterapp.b2clogin.com/worldsbestflutterapp.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_signupandin';
  final List<String> _scopes = [
    'openid',
    'offline_access',
  ];

  ////  Instance of FlutterAppAuth ////
  FlutterAppAuth _appAuth = FlutterAppAuth();

At the beginning of the loadData method we created earlier, add a call to the authorizeAndExchangeCode method on the _appAuth instance.

void loadData() async {
////  Authenticate with Azure B2C ////
  var result = await _appAuth.authorizeAndExchangeToken(
      AuthorizationTokenRequest(_clientId, _redirectUrl,
          discoveryUrl: _discoveryUrl, scopes: _scopes));

Now, the only thing left to do is to pass in the token that gets returned from the authentication process into the call to retrieve the tasks. To do this we actually need to make a minor change to the swagger generated code to tell it to use OAuth credentials. Locate the api_client.dart file, which for my project is at /tasks/lib/api_client.dart and change the ApiClient constructor as follows:

ApiClient({this.basePath: "https://localhost"}) {
  // Setup authentications (key: authentication name, value: authentication).
  _authentications['Bearer'] = new OAuth();
}

This sets the ApiClient to use OAuth which means that you can then call the setAccessToken method on the instance of the ApiClient, which should be the code that immediately follows the call to authorizeAndExchangeCode.

var client = ApiClient(basePath: _baseUrl);
client.setAccessToken(result.idToken);

And there you have it, your application should be good to go. When you launch the application, it will redirect to the Azure B2C sign in page. After authenticating it will redirect back to the application and load the task list.

image

Big shout out to the community for rolling so many great plugins for Flutter. The world of cross platform development is progressively becoming easier to make high quality applications through frameworks such as Xamarin.Forms and Flutter.


Contact Built to Roam for more information on building cross-platform applications


Cross-platform applications – PhoneGap/Cordova, Xamarin, PWAs and now Flutter

Cross-platform applications – PhoneGap/Cordova, Xamarin, PWAs and now Flutter

At Built to Roam we build applications that target a wide array of different platforms, using a wide variety of technologies depending on the project requirements. One of the most challenging things faced by organisation looking to build any form of software is often not what should I build, it’s often what technology should I use to build it. Unfortunately this is also where the most cycles are wasted and the poorest decisions are made.

Nirvana would be building the software once, and for it to be available on every platform and device type, able to be instantly updatable and have a rich and engaging user interface (and I’m sure there’s more things that needed to be bolted onto this list of “ideals”). However, the reality is that there are different device types and sizes; there are different technologies with differing capabilities; and different developer and deployment workflows. Rather than being able to make an absolute decision on the best strategy, companies are limited by their own field of influence. Too often this includes Gartner reports, media hype and both internal and contractors with whom the decision makers have a relationship with.

In addition to the number of options that are available, the optimum strategy also evolves over time. For example, five years ago in Australia it made sense for organisations to start their investment in mobile apps with a fairly basic iPhone application. Today the market expectation is that a mobile strategy encompasses at least Android and iOS, phone and tablet, and with a comprehensive set of features. In fact some applications, don’t even have a web presence, finding that their mobile apps were sufficient for their business model.

So the question is really whether it is possible to define the optimum strategy for a business and is it possible to future proof it?

To investigate this a bit further, let’s take a look at the progression of native application development. What’s quite interesting is that businesses have woken up to the fact that maintaining multiple applications written in the preferred technology for each platform is not sustainable. This has led to the emergency of a host of cross platform tools that generate native applications. There are tools such as Xamarin/Xamarin Forms which compile C# so that it can be run on the target platform; There has also been an explosion in Javascript based solutions, such as React Native where it generates the native components for each platform based on HTML mark-up (+CSS, JavaScript etc); More recently again there is Flutter, which aims to provide a user experience that has been drawn from the ground up to be platform agnostic. How do you make a decision between these technologies?

More importantly is – are you making the decision about the right thing? It would seem that making a decision about which native application toolset to use would be right but actually the web and some of the hybrid solutions solve so many challenges that native application developers face, it would be foolish to ignore them. Take for example the recent hype around Progressive Web Applications. There are some who believe this is just another round of hype about the newest buzzword to arrive on the scene but in actual fact whilst the name is new, the concept is not. Back even in the days of Windows Vista, there were desktop gadgets that essentially allowed parts of the web to run in a container in an offline capacity. PWAs are just the latest name to be given to this concept.

Where PWAs are set to make a difference is that they are being widely backed (eg Google: https://developers.google.com/web/progressive-web-apps/ and Microsoft: https://pwabuilder.com) and they also arrive at a point in time where devices have browsers and rendering engines that are capable of delivering a high-performance web experience whether in-browser, or in a hosted web application.

Do you think the market is ready for PWAs? or are native applications going to rule for the foreseeable future?