Building and running Flutter
The way an application is built is fundamental to how it will perform on the target platform. This is an important step regarding performance. Even though you do not necessarily need to know this for every kind of application, knowing how the application is built helps you understand and measure possible improvements.
As we have already pointed out, Flutter relies on the AOT compilation of Dart for release mode and the JIT compilation of Dart for development/debug mode. Dart is one of only a few languages that are capable of being compiled to both AOT and JIT, and Flutter gets the most of this advantage. Let’s look at the different build options available, why you should use each one, and how the capabilities of Dart lead to an optimal developer and user experience.
Debug mode
During development, Flutter uses JIT compilation in debug mode. Debug mode compilation is optimized for fast feedback, and therefore sacrifices execution speed and binary size (the size of the app when installed on a device). However, due to the power of Dart’s compiler, interactions between the code and the simulator/device are still fast, and debugging tools allow developers to step into the source code and analyze the widget’s layout.
Release mode
In release mode, debugging information is not necessary, and the focus is performance. Flutter uses a technique that is common to game engines. By using AOT mode, Dart code is compiled to native code, and the app loads the Flutter library and delegates rendering, input, and event handling to it through the Skia or Impeller rendering engines.
Skia versus Impeller
Skia is an open source library that provides APIs for 2D graphics. It is the primary rendering engine in Flutter and is used by Google Chrome, Android, Firefox, and many others. It is also backed by Google, similar to Dart and Flutter.
However, the unique way that Flutter uses Skia has led to some minor display issues. Additionally, Skia is solely a 2D rendering engine, which limits the potential domains that Flutter can be used for.
To solve these problems, the Flutter team has created a new rendering engine called Impeller, which removes the display issues, provides support for 3D, and is designed to harness the capabilities of modern graphics APIs.
At the time of writing, Impeller is the default rendering engine for iOS apps, with Android support being actively developed, and web support on the development roadmap.
Profile mode
Sometimes, you need to analyze the performance of your app. Profile mode retains just enough debugging ability to create a profile of your app’s performance while attempting to be a true reflection of your app’s real-world performance. This mode is only available on physical devices because emulators will not have representative performance characteristics.
Supported platforms
At the time of writing, Flutter supports ARM Android devices running at least on Jelly Bean 4.1.x version, and iOS devices from iPhone 4S or newer. As you would expect, Flutter apps can be run on device emulators, and debugging works equally well on physical and emulated devices.
Additionally, Flutter has web and desktop support (Windows, macOS, and Linux). As you can see, the vision for Flutter is to allow developers to have a single code base for mobile, web, and desktop!
We are not going to go into more detail on Flutter’s compilation aspects as they are beyond the scope of this book. For more information, you can read https://docs.flutter.dev/resources/faq#run-android and https://docs.flutter.dev/resources/faq#run-ios.
The pubspec.yaml file
The pubspec.yaml
file in Flutter is a file that is used to define Dart packages. Besides that, it contains an additional section for configurations specific to Flutter. Let’s see the pubspec.yaml
file’s content in detail:
name: hello_flutter description: A new Flutter project. publish_to: 'none' version: 1.0.0+1
The beginning of the file is simple. As we already know, the name
property is defined when we execute the pub create
command. Next is the default project’s description
; feel free to change this to something more interesting. Note that if you do so, your IDE may suddenly run the flutter pub get
command. We’ll see why in a bit.
Description during create
Like many parts of the pubspec.yaml
file, you can specify the description while running the flutter create
command by using the –
description
argument.
The publish_to
property is used if you are creating a package that you wish to share with others. We will explore this notion in later chapters when we start to use plugins for our apps. By default, packages are published to the official Dart package repository, pub.dev
.
If you set the publish_to
field to none
, this means that the package will not be published to, or listed in, the Dart package repository. This is useful for private packages that are only intended for internal use within an organization. If you are writing a mobile app instead of a package or plugin, then you would set this to none
.
The version
property follows the Dart package conventions: the version number, plus an optional build version number separated by +
. In this example, the version number is 1.0.0 and the build number is 1.
In addition to this, Flutter allows you to override these values during the build. We will take a more detailed look at this in Chapter 13, Releasing Your App to the World.
Then, we have the dependencies
section of the pubspec
file:
environment: sdk: ">=3.1.0 <4.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0
We start with the environment
property. This specifies the version of Dart that your code will work with. This entry is specifying that your code will need version 3.1.0 of Dart or above, but will not run on Dart 4.0.0. As per standard versioning, you would expect that if Dart 4.0.0 is released, it will have some backward-incompatible changes that may stop your code from compiling. This happened when Dart was updated from 1.x.x to 2.x.x. By restricting your allowed Dart versions, your code will not need to support Dart 3.x.x until you are ready to do so.
Important note
Dart 2.12 was a significant milestone for Dart because it introduced the concept of null safety. Code written before Dart 2.12 was released will have certain syntax differences and potentially be more prone to bugs around the nullness of variables. We will explore null safety in Chapter 2, An Introduction to Dart.
Additionally, Dart 3.0 introduced new language features such as interfaces and records, which will be used throughout this book. Ensure that any projects you create are using Dart 3.0 or later so that you can get full null safety and the latest Dart language features.
Then, we have the dependencies
property. This starts with the main dependency of a Flutter application, the Flutter SDK itself, which contains many of Flutter’s core packages.
As an additional dependency, the generator adds the cupertino_icons
package, which contains icon assets that are used by the built-in Flutter Cupertino widgets (there’s more on that in the next chapter). Cupertino is the name given to widgets that follow the iOS design guidelines.
As you add other dependencies (and I would bet my hat that you will add a lot of dependencies), they will also appear here.
The dev_dependencies
property contains the flutter_test
package dependency provided by the Flutter SDK itself, which contains Flutter-specific extensions to Dart’s test package
. We will explore this in Chapter 12, Testing and Debugging.
Additionally, it contains the flutter_lints
dependency. As we mentioned in the Hello World! section, linter
uses lints
to enforce a recommended code style.
In the final block of the file, there’s a dedicated flutter
section:
flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # ... # To add custom fonts to your application, add a fonts section here, # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic
This flutter
section allows us to configure resources that are bundled in the application to be used during runtime, such as images, fonts, music, sound effects, and videos.
Let’s have a closer look:
uses-material-design
: We will see the Material widgets provided by Flutter in the next chapter. Much like Cupertino is the name used for the iOS design guidelines, Material is the name given to the Android/Google design guidelines. In addition to them, we can use Material Design icons (https://material.io/tools/icons/?style=baseline), which are in a custom font format. For this to work properly, we need to activate this property (set it totrue
) so that the icons are included in the application.assets
: This property is used to list the resource paths that will be bundled with the final application. Theassets
files and folders can be organized in any way; what matters for Flutter is the path to the files. You specify the path of the file relative to the project’s root. This is used later in Dart code when you need to refer to an asset file. Here’s an example of adding a single image:assets: images/home_background.jpeg
Often, you will want to add many images, and listing them individually would be onerous. An alternative is to include a whole folder:
assets: images/
You must add the /
character at the end of the path, which is used to specify that you want to include all files in that folder. Note that this doesn’t include subfolders; they would need to be listed as well:
assets: images/ images/icons/
fonts
: This property allows us to add custom fonts to the application. More on this will be covered in Chapter 6, Handling User Input and Gestures, in the Custom fonts section.
We will learn how to load different assets throughout this book whenever we need to. You can read more about asset specification details on the Flutter docs website: https://flutter.io/docs/development/ui/assets-and-images.
Running the generated project
The hello_world
application that we created earlier has a counter to demonstrate the React style of programming in Flutter. We will look at Dart code in more detail in the next chapter, but let’s look at the main.dart
file a little bit more before we try running the application.
The lib/main.dart file
We explored the main.dart
file earlier to look at a widget. This file is also the entry point of the Flutter application:
void main() => runApp(MyApp());
The main
function is the Dart entry point of an application; this is where the execution of your app will start. Flutter then takes over the execution in the runApp
function, which is called by passing your top-level (or root) widget as a parameter. This is the widget we saw earlier – the MyApp
widget.
Flutter run
To execute a Flutter application, we must have a connected device or simulator. You can see if your system has been set up correctly to use simulators or emulators by running the flutter doctor
tool. The flutter emulators
tool can then be run, which will show which emulators/simulators are on your system. The following command lets you know the existing Android and iOS emulators that can be used to run the project:
flutter emulators
You will get something similar to the following output:
Figure 1.7 – Output from the flutter emulators command
You can then choose to run the emulator/simulator using the command shown in the output or start the emulator or simulator using the Android Studio or Xcode simulator functionality. In this scenario, you could run the following:
flutter emulators --launch Pixel_6_API_31
You can learn how to manage your Android emulators at https://developer.android.com/studio/run/managing-avds. For iOS device simulators, you should use the Xcode Simulator developer tool.
Emulator versus simulator
You will notice that Android has emulators and iOS has simulators. The Android emulator mimics the software and hardware of an Android device. In contrast, the iOS simulator only mimics the software of an iOS device, using the full hardware available on the machine it is running. Therefore, it is highly recommended that you test your app on a true iOS device before releasing it to the world to ensure there are no hardware issues, such as excessive memory consumption.
Alternatively, you can choose to run the app on a physical device. You will need to set up your device for development, so for the moment, it is probably easier to use an emulator or simulator.
After asserting that we have a device connected that can run the app, we can use the following command from the project folder to run the app on the device:
flutter run
You will see an output similar to the following:
Figure 1.8 – Output from the flutter run command
This command starts the debugger and makes the hot reload functionality available. The first run of the application will generally take a little longer than subsequent executions.
The emulator or simulator should start up and after a pause to load the OS, it should run your Flutter application. If you see the following screen, then congratulations – you have just run your first-ever Flutter application and should be proud of yourself!
Figure 1.9 – Emulator displaying the Flutter app
The application is up and running; you can see a debug mark in the top-right corner. This shows that it’s not a release version running; the app is in debug mode, which means you have all the debug mode goodies available to you, such as hot reload and code debug facilities.
The preceding example was run on a Pixel 6 emulator. The same result can be achieved using an iOS simulator, or any other Android virtual device (AVD).