Flutter-秘籍-一-

528 阅读29分钟

Flutter 秘籍(一)

原文:Flutter Recipes

协议:CC BY-NC-SA 4.0

一、开始

本章中的食谱帮助你设置你的本地开发环境,为构建 Flutter 应用做好准备。根据机器的操作系统,设置步骤可能会有所不同。你只需要按照你自己的要求使用食谱。在使用了本章中的方法之后,你应该能够在模拟器或者物理设备上运行第一个 Flutter 应用。

1.1 在 Windows 上安装 Flutter SDK

问题

你有一台 Windows 机器,你想在这台机器上开始 Flutter 开发。

解决办法

在 Windows 机器上安装 Flutter SDK,设置 Android 平台。

讨论

Flutter SDK 支持 Windows 平台。在 Windows 上安装 Flutter 并不像你想象的那样困难。首先,您需要确保您的本地开发环境满足最低要求。您需要 64 位 Windows 7 SP1 或更高版本,以及至少 400MB 的可用磁盘空间供 Flutter SDK 使用。Flutter SDK 还要求 Windows PowerShell 5.0 或更高版本以及 Git for Windows 在机器上可用。

Windows PowerShell 5.0 预装了 Windows 10。对于 Windows 10 之前的 Windows 版本,需要按照微软的说明( https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell )手动安装 PowerShell 5.0。您可能已经安装了 Git for Windows,因为 Git 是一个非常流行的开发工具。如果您可以在 PowerShell 中运行 Git 命令,那么您就可以开始了。否则需要下载 Git for Windows ( https://git-scm.com/download/win )并安装。安装 Git for Windows 时,请确保在“调整路径环境”页面中选择了“从命令行和第三方软件安装 Git”选项;见图 1-1 。

img/479501_1_En_1_Fig1_HTML.jpg

图 1-1

Git for Windows 安装程序

满足这些最低要求后,您可以从官方网站( https://flutter.dev/docs/get-started/install/windows )下载 Flutter SDK zip 包。将下载的 zip 文件解压缩到本地计算机上的所需位置。建议避免使用安装了 Windows 的系统驱动程序。在提取的目录中,双击文件flutter_console.bat启动 Flutter 控制台并运行 Flutter SDK 命令。

为了能够在任何 Windows 控制台中运行 Flutter SDK 命令,我们需要将 Flutter SDK 添加到PATH环境变量中。安装目录的bin的完整路径应该添加到PATH中。要在 Windows 10 上修改路径

  1. 打开开始搜索,键入“env”并选择“编辑系统环境变量”。

  2. 单击“环境变量…”按钮,在“系统变量”部分的第一列中找到带有“路径”的行。

  3. 在“编辑环境变量”对话框中,点击“新建”,输入已安装的 Flutter SDK 的 bin 目录的路径。

  4. 单击“确定”关闭所有对话框。

现在,您可以打开一个新的 PowerShell 窗口并键入命令flutter --version来验证安装;见图 1-2 。

img/479501_1_En_1_Fig2_HTML.jpg

图 1-2

在 Windows 上成功安装 Flutter SDK

Windows 上仅支持 Android 平台。按照配方 1-7 继续设置。

1.2 在 Linux 上安装 Flutter SDK

问题

你有一台 Linux 机器,你想在这台机器上开始 Flutter 开发。

解决办法

在 Linux 机器上安装 Flutter SDK,设置 Android 平台。

讨论

Flutter SDK 支持 Linux 平台。然而,鉴于有许多不同的 Linux 发行版可用,安装 Flutter SDK 的实际步骤可能会略有不同。这个方法是基于在 LTS 的 Ubuntu 18.04 上安装 Flutter SDK。

Flutter SDK 需要几个命令行工具在本地环境中可用,包括bashmkdirrmgitcurlunzipwhich。对于大多数 Linux 发行版,默认情况下应该已经包含了命令bashmkdirrmunzipwhich。验证这一点最简单的方法是打开一个终端窗口,键入这些命令来查看输出。如果没有安装命令,您会看到“找不到命令”错误。gitcurl不太可能默认包含。大多数 Linux 发行版都提供了内置的包管理器来安装这些工具。对于 Ubuntu,可以使用apt-get;请参见以下命令。

$ sudo apt-get update
$ sudo apt-get install -y curl git

安装成功完成后,您可以键入命令curlgit进行验证。

现在你可以从官方网站( https://flutter.dev/docs/get-started/install/linux )下载 Flutter SDK 压缩包。将下载的 zip 文件解压缩到本地计算机上的所需位置。打开终端窗口,导航到提取的 Flutter SDK 的目录,并运行以下命令来验证安装。

$ bin/flutter --version

建议将 Flutter SDK 的bin目录添加到PATH环境变量中,这样flutter命令可以在任何终端会话中运行。对于 Ubuntu,可以编辑文件~/.profile

$ nano ~/.profile

将下面一行添加到该文件并保存。

export PATH="<flutter_dir>/bin:$PATH"

在当前终端窗口中,您需要运行source ~/.profile以使更改生效。或者您可以简单地创建一个新的终端窗口。在任何终端窗口中键入flutter --version进行验证。你会看到与图 1-2 相同的输出。

Linux 上只支持 Android 平台。按照配方 1-7 继续设置。

1.3 在 macOS 上安装 Flutter SDK

问题

你有一台 macOS 机器,你想在这台机器上开始 Flutter 开发。

解决办法

安装 Flutter SDK,在 macOS 机器上设置 Android 和 iOS 平台。

讨论

对于 macOS,Flutter SDK 需要几个命令行工具在本地环境中可用。这些工具是bashmkdirrmgitcurlunzipwhich。macOS 系统中应该已经有这些工具了。您可以简单地在终端中键入这些命令进行验证。安装缺失工具最简单的方法就是使用自制软件( https://brew.sh/ )。在设置 iOS 开发环境时,自制软件也很重要。使用brew install安装工具,例如brew install git安装 Git。

安装所需工具后,我们可以从官方网站( https://flutter.dev/docs/get-started/install/macos )下载 Flutter SDK 压缩包。将下载的 zip 文件解压缩到本地计算机上的所需位置。flutter命令位于提取位置的bin目录下。

要在任何终端会话中运行flutter命令,应该更新PATH环境变量以包含 Flutter SDK 的bin目录。这通常通过更新壳的轮廓来完成。对于默认的 bash,这个文件是~/.bash_profile。对于 zsh 来说,这个文件就是~/.zshrc。修改该文件以包含以下行。

export PATH=<flutter_install_dir>/bin:$PATH

要让当前终端窗口使用更新后的PATH,需要运行source ~/.bash_profile。您也可以启动一个新的终端窗口,该窗口将自动使用PATH的更新值。

在任一终端窗口中运行flutter --version来验证安装。您将看到与图 1-2 相同的输出。

macOS 上同时支持 Android 和 iOS 平台。按照配方 1-4 和 1-7 继续设置。

1.4 设置 iOS 平台

问题

你想为 iOS 平台开发 Flutter 应用。

解决办法

在 Mac 上为 Flutter SDK 设置 iOS 平台。

讨论

要为 iOS 开发 Flutter 应用,你需要有一台至少安装了 Xcode 9.0 的 Mac。要设置 iOS 平台,您需要完成以下步骤:

img/479501_1_En_1_Fig3_HTML.jpg

图 1-3

Flutter 医生的输出

  1. 从 App Store 安装 Xcode ( https://developer.apple.com/xcode/ )。

  2. 验证 Xcode 命令行工具的路径。运行以下命令显示命令行工具的当前路径。通常你应该会看到类似/Applications/Xcode.app/Contents/Developer的输出。

    $ xcode-select -p
    
    

    如果输出中显示的路径不是您想要的路径,例如,您安装了不同版本的 Xcode 命令行工具,请使用xcode-select -s切换到不同的路径。如果没有安装命令行工具,使用xcode-select --install打开安装对话框。

  3. 您需要打开 Xcode 一次以接受其许可协议。或者您可以选择运行命令sudo xcodebuild -license来查看并接受它。

  4. Flutter SDK 需要 iOS 平台的其他工具,包括 libimobiledevice、usbmuxd、ideviceinstaller、ios-deploy、CocoaPods ( https://cocoapods.org/ )。所有这些工具都可以用自制软件安装。如果您运行命令flutter doctor,它会显示使用 Homebrew 安装这些工具的命令。只需运行这些命令并使用flutter doctor再次检查。当你看到“iOS toolchain”的绿色勾号时,iOS 平台设置成功,可供 Flutter SDK 使用;样本输出见图 1-3 。

1.5 设置 iOS 模拟器

问题

你需要一个快速的方法来测试 iOS 平台上的 Flutter 应用。

解决办法

设置 iOS 模拟器。

讨论

Xcode 为不同的 iOS 版本提供模拟器。您可以使用 Xcode ➤偏好设置中的标签组件下载其他模拟器。要打开模拟器,请运行以下命令。

$ open -a Simulator

当模拟器打开时,您可以使用菜单硬件设备切换不同设备和 iOS 版本的组合。

模拟器启动后,运行flutter devices应该会显示模拟器。

1.6 设置 iOS 设备

问题

你已经在 iOS 模拟器上完成了你的 Flutter 应用的测试,你想在真实的 iOS 设备上测试它们。

解决办法

将 Flutter 应用部署到 iOS 设备。

讨论

在将 Flutter 应用部署到 iOS 设备之前,您需要运行flutter doctor来验证 iOS 工具链是否设置正确。要在设备上开发和测试 Flutter 应用,你需要有一个 Apple ID。如果您想将应用分发到 App Store,您还需要注册 Apple 开发者计划。

第一次连接物理设备进行 iOS 开发时,您需要信任 Mac 来连接您的设备。Flutter 应用需要在部署到设备之前进行签名。在 Xcode 中打开 Flutter app 的ios/Runner.xcworkspace文件。在常规选项卡中,在签约部分选择正确的团队。如果您选择连接的设备作为运行目标,Xcode 将完成代码签名的必要配置。捆绑标识符必须是唯一的。

img/479501_1_En_1_Fig4_HTML.jpg

图 1-4

Xcode 中的应用签名

Flutter 应用可以使用 Xcode 或命令flutter run部署到设备上。首次部署应用时,您可能需要信任 iOS 设备上设置应用的通用设备管理中的开发证书。

1.7 设置 Android 平台

问题

你想为 Android 平台开发 Flutter 应用。

解决办法

安装 Android Studio,在本地机器上设置 Android 平台。

讨论

要开发 Android 平台的 Flutter apps,首先需要设置 Android 平台。Flutter SDK 由于其 Android 平台依赖性,需要完整安装 Android Studio,所以我们必须安装 Android Studio。

进入 ANDROID STUDIO 下载页面( https://developer.android.com/studio/ ),点击“下载 Android Studio”按钮。您需要接受条款和条件才能下载。下载页面会检查您的平台,并提供最适合下载的版本。如果提供的选项不是您想要的,请单击“下载选项”并从所有下载选项列表中选择;见图 1-5 。

img/479501_1_En_1_Fig5_HTML.jpg

图 1-5

Android Studio 的下载选项

Android Studio 提供了一个基于 GUI 的安装程序,所以在本地机器上安装和运行它非常容易。安装 Android Studio 还会安装 Android SDK、Android SDK 平台工具和 Android SDK 构建工具。即使你选择不使用 Android Studio 作为 IDE,Android 开发仍然需要 Android SDK 和相关工具。

在 Android Studio 中首选项的 Android SDK 页面,还可以安装额外的 Android SDK 平台和工具;参见图 1-6 。Android Studio 还会提示已安装的 Android SDK 平台和工具的可用更新。

img/479501_1_En_1_Fig6_HTML.jpg

图 1-6

在 Android Studio 中管理 Android SDK

1.8 设置 Android 模拟器

问题

你需要一种快速的方法来测试 Android 平台的 Flutter 应用。

解决办法

设置 Android 模拟器。

讨论

开发 Flutter 应用时,可以在 Android 模拟器上运行,看看应用运行的结果。要设置 Android 模拟器,您可以完成以下步骤。

在 Android Studio 中打开一个 Android 项目,选择工具 ➤ Android ➤ AVD 管理器打开 AVD 管理器,点击“创建虚拟设备…”;见图 1-7 。

img/479501_1_En_1_Fig7_HTML.jpg

图 1-7

Android 虚拟设备管理器

选择一个设备定义,比如 Nexus 6P,点击下一步;参见图 1-8 。

img/479501_1_En_1_Fig8_HTML.jpg

图 1-8

选择硬件

为您想要模拟的 Android 版本选择一个系统映像,然后单击“下一步”;参见图 1-9 。

img/479501_1_En_1_Fig9_HTML.jpg

图 1-9

选择一个系统映像

为“仿真性能”选择“硬件- GLE 2.0”以启用硬件加速,然后单击“完成”;参见图 1-10 。

img/479501_1_En_1_Fig10_HTML.jpg

图 1-10

选择模拟性能

一个新的 AVD 被创建并在 AVD 管理器中列出。Android Studio 官方网站提供了关于如何管理 AVD 的全面指南( https://developer.android.com/studio/run/managing-avds ),如果你想了解关于 AVD 配置的更多细节。

在 AVD 管理器中,单击绿色三角形按钮启动模拟器。模拟器启动并显示默认的 Android 主屏幕可能需要一些时间。

1.9 设置 Android 设备

问题

您已经在模拟器上完成了对 Flutter 应用的测试,并且您想要在真实的 Android 设备上测试它们。

解决办法

设置您的 Android 设备运行 Flutter 应用。

讨论

要设置您的 Android 设备,您可以完成以下步骤:

  1. 您需要在设备上启用开发人员选项和 USB 调试。查看安卓官方网站上的说明( https://developer.android.com/studio/debug/dev-options#enable )。你可能还需要在 Windows 机器上安装谷歌 USB 驱动( https://developer.android.com/studio/run/win-usb )。

  2. 使用 USB 电缆将您的设备插入电脑。设备会提示一个对话框询问权限,授权您的电脑访问您的设备。

  3. 运行命令flutter devices来验证 Flutter SDK 可以识别你的设备。

可以使用 Android Studio 或命令flutter run将 Flutter 应用部署到设备上。

1.10 使用命令行创建 Flutter 应用

问题

您已经设置了本地环境来开发 Flutter 应用。即使使用 Android Studio 或 VS 代码是一个很好的开发选择,您可能仍然想知道如何从命令行完成这项工作。

解决办法

使用 Flutter SDK 中的命令创建和构建 Flutter 应用。

讨论

使用像 Android Studio 和 VS Code 这样的工具可以让 Flutter 开发变得容易得多。然而,知道如何使用命令行工具构建 Flutter 应用仍然很有价值。这对持续集成很重要。它还允许您使用任何其他编辑器来开发 Flutter 应用。

命令flutter create可以用来创建一个新的 Flutter app。实际上,Android Studio 和 VS Code 都使用这个命令来创建新的 Flutter 应用。下面的命令在目录flutter_app中创建新的 Flutter 应用。

$ flutter create flutter_app

该命令在指定目录中创建各种文件,作为新应用的框架代码。导航到目录flutter_app并使用flutter run运行该应用。

1.11 使用 Android Studio 创建 Flutter 应用

问题

在开发 Flutter 应用时,您希望拥有一个强大的 IDE 来满足大多数需求。

解决办法

使用 Android Studio 创建 Flutter 应用。

讨论

既然我们已经安装了 Android Studio 来为 Flutter SDK 搭建 Android 平台,那么使用 Android Studio 作为开发 Flutter 应用的 IDE 是一个很自然的选择。Android Studio 本身就是一个基于 IntelliJ 平台的强大 IDE。如果你使用过 JetBrains 的其他产品,如 IntelliJ IDEA 或 WebStorm,你可能会发现使用 Android Studio 非常容易。

要使用 Android Studio 进行 Flutter 开发,需要 Flutter 和 Dart 插件。要安装这两个插件,在 Android Studio 的首选项对话框中打开插件页面,点击“浏览存储库…”按钮。在打开的对话框中,输入“Flutter”搜索要安装的 Flutter 插件;见图 1-11 。单击绿色的安装按钮进行安装。这也将提示您安装 Dart 插件。单击“是”也安装它。重启 Android Studio。

img/479501_1_En_1_Fig11_HTML.jpg

图 1-11

在 Android Studio 中安装 Flutter 插件

重启 Android Studio 后,你应该会看到一个新的选项来启动一个新的 Flutter 项目。Flutter 项目向导有不同的页面来配置新项目。

第一页允许你选择新的 Flutter 项目的类型。页面中的描述显示了这四种不同项目类型的区别。大多数时候,我们会创建一个 Flutter 应用。

img/479501_1_En_1_Fig12_HTML.jpg

图 1-12

选择 Flutter 项目的类型

第二页允许您定制新的 Flutter 项目的基本配置,包括项目名称、位置和描述。

img/479501_1_En_1_Fig13_HTML.jpg

图 1-13

基本项目配置

最后一页允许您定制一些高级项目配置。公司域用于创建项目的唯一标识符。

img/479501_1_En_1_Fig14_HTML.jpg

图 1-14

高级项目配置

完成向导后,一个新项目被创建并在 Android Studio 中打开。

1.12 使用 VS 代码创建 Flutter 应用

问题

你想用一个轻量级的编辑器来开发 Flutter 应用。

解决办法

使用 VS 代码创建 Flutter 应用。

讨论

VS Code ( https://code.visualstudio.com/ )是前端开发者社区里比较流行的轻量级编辑器。通过对 Flutter 和 Dart 的扩展,我们也可以使用 VS 代码进行 Flutter 开发。在 VS 代码中打开 Extensions 选项卡,搜索“flutter”安装 Flutter 扩展;参见图 1-15 。Flutter 延伸取决于 Dart 延伸,也将安装。安装完这两个扩展后,我们可以打开命令面板,搜索“flutter”来获得可用的 Flutter 命令。

img/479501_1_En_1_Fig15_HTML.jpg

图 1-15

在 VS 代码中安装 Flutter 扩展

要在 VS 代码中创建新的 Flutter,打开命令面板并运行 Flutter: New Project 命令。在打开的对话框中输入新项目的名称。选择项目的目录。VS 代码为新创建的项目打开一个新窗口。

1.13 运行 Flutter 应用

问题

你想在模拟器或设备上运行 Flutter 应用。

解决办法

使用flutter run命令或 ide 运行 Flutter 应用。

讨论

根据您开发 Flutter 应用的首选方法,有不同的方法来运行 Flutter 应用。在运行 Flutter 应用之前,您必须至少有一个正在运行的仿真器或连接的设备:

img/479501_1_En_1_Fig16_HTML.jpg

图 1-16

在 Android Studio 中选择设备

  • 命令flutter run启动当前的 Flutter 应用。

  • 在 Android Studio 中,从图 1-16 所示的下拉菜单中选择仿真器或设备,然后点击运行按钮启动应用。

  • 在 VS 代码中,选择调试启动不调试启动 app。

1.14 了解 Flutter 应用的代码结构

问题

你想知道 Flutter 应用的典型结构。

解决办法

浏览 Flutter SDK 生成的样例 app,了解文件。

讨论

在深入开发 Flutter 应用的细节之前,您应该了解 Flutter 应用的代码结构,这样您就知道在哪里添加新文件。Flutter 应用为应用中的各种文件提供了预定义的目录结构。当一个新的应用创建时,你可以看看生成的文件,并对它们有一个基本的了解。表 1-1 显示了创建的 app 的目录和文件。

表 1-1

一个 Flutter app 的目录和文件

|

名字

|

描述

| | --- | --- | | lib | app 源代码主目录。文件main.dart通常是 app 的入口点。 | | test | 包含测试文件的目录。 | | android | Android 平台的文件。 | | ios | iOS 平台的文件。 | | pubspec.yaml | Dart 发布工具的包描述。 | | pubspec.lock | Dart 发布工具的锁定文件。 | | .metadata | Flutter SDK 使用的 Flutter 项目描述。 |

1.15 修复 Flutter SDK 的配置问题

问题

您希望确保本地开发环境的配置对于 Flutter 开发是正确的。

解决办法

使用命令flutter doctor

讨论

安装 Flutter SDK 后,需要配置其他支持工具。命令flutter doctor是提供必要帮助的主要工具。该命令检查本地环境并报告 Flutter SDK 安装的状态。对于发现的每个问题,它还会给出如何修复它们的说明。您需要做的就是应用建议的修复并再次运行flutter doctor来验证结果。没有必要修复flutter doctor报告的所有问题。如果一些问题不相关,您可以放心地忽略它们。例如,如果您不打算使用 VS 代码作为主要的 IDE,那么是否安装 VS 代码并不重要。

1.16 摘要

本章的食谱提供了如何让你的本地机器为 Flutter 应用开发做好准备的指导。flutter doctor是一个有用的设置工具。通过遵循此命令提供的说明,您应该能够修复大多数配置问题。在下一章,我们将看到使用 Dart SDK、Flutter SDK 和 IDEs 提供的工具的方法。

二、了解工具

没有各种工具的帮助,构建 Flutter 应用是不可能成功的。在开发过程中,我们可能需要使用 Dart SDK、Flutter SDK 和 IDEs 中的工具。善用这些工具可以提高你的生产力。本章涵盖了 Dart SDK、Flutter SDK、Android Studio 和 VS Code 工具的使用。

2.1 使用 Dart 天文台

问题

你想知道一个正在运行的 Flutter app 的内部情况。

解决办法

使用 Dart SDK 提供的 Dart 天文台。

讨论

Dart Observatory 是 Dart SDK 提供的工具,用于分析和调试 Dart 应用。由于 Flutter 应用也是 Dart 应用,所以 Observatory 也可以用于 Flutter 应用。Observatory 是调试、跟踪和分析 Flutter 应用的重要工具。天文台允许你

  • 查看应用的 CPU 配置文件。

  • 查看应用的内存分配配置文件。

  • 交互式调试应用。

  • 查看应用堆的快照。

  • 查看应用生成的日志。

当使用flutter run启动 Flutter app 时,Observatory 也会启动并等待连接。您可以指定 Observatory 监听的端口,或者让它默认监听一个随机端口。您可以在命令输出中看到访问天文台的 URL。在浏览器中导航到网址,就可以看到天文台的 UI。

注意

为了获得最佳效果,建议在使用天文台时使用谷歌浏览器。其他浏览器可能无法正常工作。

观察站用户界面的顶部显示 Dart 虚拟机信息;见图 2-1 。点击刷新按钮更新信息。

img/479501_1_En_2_Fig1_HTML.jpg

图 2-1

灾难援助反应队天文台的虚拟机信息

底部显示了分离株列表;参见图 2-2 。每个 Flutter 应用的入口点文件都有一个初始隔离。对于每个隔离,饼图显示虚拟机活动的明细。在饼图的右侧,有一个链接列表指向其他天文台功能的不同屏幕。

img/479501_1_En_2_Fig2_HTML.jpg

图 2-2

在 Dart 天文台隔离信息

这些观察屏幕的细节超出了本食谱的范围;参考官方文档( https://dart-lang.github.io/observatory/ )进行说明。

2.2 使用热重装和热重启

问题

当开发 Flutter 应用时,在您进行了一些代码更改后,您希望快速看到结果。

解决办法

使用 Flutter SDK 提供的热重装和热重启。

讨论

在构建移动应用时,能够有效地查看代码更改的效果至关重要,尤其是在构建 UI 时。这使我们能够快速看到实际的 UI 并迭代地更新代码。在更新应用时,保持应用的当前状态也非常重要。否则,手动将应用重置到之前的状态并继续测试会非常痛苦。假设您正在开发的组件只有注册用户可以访问,为了实际测试该组件,如果在应用更新之间没有保留状态,您可能需要在每次进行代码更改时登录。

Flutter SDK 提供的热重载是一个杀手级特性,可以显著提高开发人员的工作效率。使用热重新加载,应用更新之间的状态是反常的,因此您可以立即看到 UI 更新,并从您进行更改的最后一个执行点继续开发和测试。

根据 Flutter 应用的启动方式,有不同的方式来触发热重装。只有调试模式下的 Flutter 应用可以热重装:

  • 当 app 被命令flutter run启动时,在终端窗口输入r触发热重装。

  • 当 Android Studio 启动应用时,保存文件会自动触发热重装。也可以点击Flutter 热重装按钮手动触发。

  • 当应用由 VS 代码启动时,保存文件会自动触发热重装。也可以用键盘快捷键 Control-F5 运行命令 Flutter: Hot Reload 来手动触发。

如果应用热重新加载成功,您可以在控制台中看到热重新加载的详细信息。图 2-3 显示了在 Android Studio 中保存文件触发热重装时的控制台输出。

img/479501_1_En_2_Fig3_HTML.jpg

图 2-3

热重装输出

热重新加载非常有用,您可能希望它对您所做的所有代码更改都可用。不幸的是,仍有一些情况下热重装可能不起作用:

  • 您的代码更改会引入编译错误。您需要修复这些编译错误,热重装才能继续。

  • 热重新加载会保留应用状态,并尝试使用保留的状态来重新构建小部件树,以反映新的更改。如果您的代码更改修改了状态,那么对小部件的更改可能无法使用旧的保留状态。假设我们有一个小部件,用于显示用户的个人资料信息。在以前的版本中,用户的状态只包含用户名和名称。在新版本中,状态被更新以包括新属性email,并且小部件被更新以显示新属性。热重新加载后,小部件仍然使用旧状态,看不到新属性。在这种情况下,需要热重启来获取状态变化。

  • 对全局变量和静态字段的初始值设定项的更改只能在热重启后反映出来。

  • 对应用的main()方法的更改可能只有在热重启后才会反映出来。

  • 当枚举类型更改为常规类或常规类更改为枚举类型时,不支持热重新加载。

  • 更改类型的泛型声明时,不支持热重新加载。

如果热重新加载不起作用,您仍然可以使用热重启,这将从头重新启动应用。您可以确保热重启将反映您所做的所有更改。根据 Flutter 应用的启动方式,触发热重启有不同的方式:

  • 当 app 被flutter run启动后,在终端窗口输入R触发热重启。

  • Android Studio 启动 app 时,点击Flutter 热重启按钮触发热重启。

  • 当 VS 代码启动 app 时,点击重启按钮,或者从命令面板运行命令Flutter:****Hot Restart触发热重启。

2.3 升级 Flutter SDK

问题

您希望让 Flutter SDK 保持最新,以获得最新的特性、错误修复和性能改进。

解决办法

跟踪不同的 Flutter SDK 通道并升级 SDK。

讨论

有时,我们可能需要升级 Flutter SDK 以获得新功能、错误修复和性能改进。Flutter SDK 有不同的渠道获取更新。每个通道实际上是 Flutter SDK 的存储库中的一个 Git 分支。执行命令flutter channel显示所有可用通道;见图 2-4 。标有星形符号的频道是当前频道。在图 2-4 中,电流通道为stable

img/479501_1_En_2_Fig4_HTML.jpg

图 2-4

命令的输出flutter channel

表 2-1 显示了 Flutter SDK 的四个通道。

表 2-1

Flutter SDK 通道

|

引导

|

描述

| | --- | --- | | stable | 稳定构建的渠道。这是产品开发的推荐渠道。 | | beta | 上个月最佳构建频道。 | | dev | 最新全面测试版本的通道。在此通道中运行的测试比master多。 | | master | 积极开发最新变化的渠道。如果您想尝试最新的功能,这是要跟踪的频道。这个通道中的代码通常工作正常,但有时可能会意外中断。使用本频道风险自担。 |

我们可以使用命令flutter channel [<channel-name>]切换到不同的频道。例如,flutter channel master切换到master频道。要获得当前通道的更新,运行命令flutter upgrade。以下命令显示了切换通道的典型方式。

$ flutter channel master
$ flutter upgrade

2.4 在 Android Studio 中调试 Flutter 应用

问题

您正在使用 Android Studio 开发 Flutter 应用,并希望找出代码无法按您预期的方式运行的原因。

解决办法

使用 Android Studio 内置的 Flutter 调试支持。

讨论

调试是开发人员日常工作的重要组成部分。调试时,我们可以在运行时看到实际的代码执行路径,并检查变量值。如果您有使用其他编程语言的经验,您应该已经具备了基本的调试技能。

在 Android Studio 中,你可以点击编辑器中某一行的左边来添加断点。点击调试图标或者使用菜单运行调试在调试模式下启动 app 见图 2-5 。

img/479501_1_En_2_Fig5_HTML.jpg

图 2-5

单击调试图标开始调试

一旦代码执行遇到断点,执行就会暂停。您可以检查变量值,并使用调试工具栏中的按钮交互式地继续执行。在调试模式下,有不同的面板可以查看相关信息。

图 2-6 中的帧视图显示了当前执行的帧。

img/479501_1_En_2_Fig6_HTML.jpg

图 2-6

Android Studio 中的框架视图

图 2-7 中的变量视图显示变量和对象的值。在这个视图中,我们还可以添加表达式来监视值。

img/479501_1_En_2_Fig7_HTML.jpg

图 2-7

Android Studio 中的变量视图

图 2-8 中的控制台视图显示了显示在控制台上的信息。

img/479501_1_En_2_Fig8_HTML.jpg

图 2-8

Android Studio 中的控制台视图

2.5 在 Android Studio 中查看 Flutter 应用的概要

问题

你希望看到 Flutter 应用的轮廓,以便清楚地了解小部件是如何组织的。

解决办法

在 Android Studio 中使用 Flutter Outline 视图。

讨论

在 Android Studio 中,可以从菜单视图工具窗口Flutter 轮廓打开 Flutter 轮廓视图。此视图显示当前打开文件的树状层次结构;见图 2-9 。Flutter 轮廓视图与文件编辑器相链接。在 Flutter Outline 视图中选择一个元素会使编辑器滚动并突出显示这个元素的源代码。此链接是双向的;编辑器中的选择也会导致在 Flutter 轮廓视图中选择相应的元素。

img/479501_1_En_2_Fig9_HTML.jpg

图 2-9

Android Studio 中的 Flutter 轮廓视图

Flutter Outline 视图中的工具栏有不同的操作来管理小部件。例如,中心小部件按钮用一个Center小部件包装当前小部件。

2.6 调试 VS 代码中的 Flutter 应用

问题

您正在使用 VS 代码开发 Flutter 应用,并且想要找出为什么代码没有按照您预期的方式工作。

解决办法

使用 VS 代码中内置的 Flutter 调试支持。

讨论

在 VS 代码中,你可以点击编辑器中某一行的左边来添加断点。使用菜单调试开始调试在调试模式下启动应用。

图 2-10 显示了调试模式下的 VS 代码视图。此视图中有不同的面板:

  • 变量–显示变量的值。

  • 观察–管理观察表达式并查看其值。

  • 调用堆栈–查看当前调用堆栈。

  • 断点–添加断点的视图。

  • 调试控制台–查看输出到控制台的消息。

顶部的操作栏包含的操作包括继续、单步执行、单步执行、单步执行、重新启动和停止。

img/479501_1_En_2_Fig10_HTML.jpg

图 2-10

在 VS 代码中调试

2.7 创建 Flutter 项目

问题

您想要创建不同类型的 Flutter 项目。

解决办法

使用带有不同参数的命令flutter create

讨论

flutter create是 Flutter SDK 提供的创建 Flutter 项目的命令。在菜谱 1-10 中,我们使用这个命令来创建一个简单的 Flutter 应用。在配方 1-11 中,我们还看到了 Android 提供的向导来创建新的 Flutter 项目,它允许对已创建的项目进行定制。在引擎盖下,Android Studio 也使用了flutter create命令。该命令支持不同场景的不同参数。以下代码是flutter create的基本用法。输出目录将包含新项目的文件。

$ flutter create <output directory>

项目类型

使用参数-t--template指定要创建的项目类型。有四种类型的项目;见表 2-2 。

表 2-2

Flutter 项目类型

|

项目类型

|

描述

| | --- | --- | | app | Flutter 应用。这是默认类型。 | | package | 一个包含模块化 Dart 代码的可共享的 Flutter 项目。 | | plugin | 一个可共享的 Flutter 项目,包含 Android 和 iOS 平台特定的代码。 |

下面的命令显示了如何创建一个 Flutter 包和插件。

$ flutter create -t package my_package
$ flutter create -t plugin my_plugin

在创建插件的时候,我们也可以使用参数-i或者--ios-language来指定 iOS 代码的编程语言。Objective-C 的可能值为objc,Swift 的可能值为swift。默认值为objc。对于 Android 代码,我们可以用自变量-a或者--android-language来指定 Android 代码的编程语言。可能的值是 Java 的java和 Kotlin 的kotlin。默认值为java。以下命令显示了如何使用 Swift for iOS 和 Kotlin for Android 创建一个 Flutter 插件。

$ flutter create -t plugin -i swift -a kotlin my_plugin

代码示例

当创建一个 Flutter 应用时,我们可以使用参数-s--sample来指定样本代码作为新应用的文件lib/main.dart。给定一个样本 id,该命令尝试加载 URL 为 https://docs.flutter.dev/snippets/<sample_id>.dart 的 dart 文件。

项目配置

创建项目时有一些常规配置可用;参见表 2-3 。

表 2-3

Flutter 项目配置

|

争吵

|

描述

|

缺省值

| | --- | --- | --- | | --project-name | 这个新的 Flutter 项目的名称。该名称必须是有效的 dart 包名称。 | 从输出目录名派生 | | --org | 这个新的 Flutter 项目的组织名称。该值应该采用反向域表示法,例如,com.example。该值用作 Android 代码的 Java 包名和 iOS 包标识符中的前缀。 | com.example | | --description | 这个新 Flutter 项目的描述。 | 新的 Flutter 项目 |

以下命令使用表 2-3 中的项目配置。

$ flutter create --org=com.mycompany --description="E-commerce app" my_ecommerce_app

启用或禁用功能

有额外的标志来启用或禁用某些功能;见表 2-4 。一次只能指定每对中的一个参数。前缀为--no的参数名表示禁用一个特性,另一个表示启用一个特性。例如,--overwrite表示启用覆盖,--no-overwrite表示禁用覆盖。默认值“开”或“关”分别表示默认情况下该功能是启用还是禁用。例如,--overwrite--no-overwrite对的默认值 Off 表示默认使用--no-overwrite

表 2-4

flutter create的特点

|

争论

|

描述

|

缺省值

| | --- | --- | --- | | --overwrite / --no-overwrite | 是否覆盖现有文件。 | 离开 | | --pub / --no-pub | 项目创建后是否运行flutter packages get。 | 在 | | --offline / --no-offline | 是否在离线模式下运行flutter packages get。仅在--pub开启时适用。 | 离开 | | --with-driver-test / --no-with-driver-test | 是否添加flutter_driver依赖,生成样本 Flutter 驱动测试。 | 离开 |

2.8 运行 Flutter 应用

问题

你想运行 Flutter 应用。

解决办法

使用带有不同参数的命令flutter run

讨论

flutter run是 Flutter SDK 提供的启动 Flutter apps 的命令。flutter run针对不同的使用场景有很多说法。

不同的构建风格

默认情况下,flutter run会构建应用的调试版本。调试版本适用于支持热重装的开发和测试。对于不同的场景,您可以使用其他的构建风格;见表 2-5 。

表 2-5

构建 Flutter 运行的风味

|

争吵

|

描述

| | --- | --- | | --debug | 调试版本。这是默认的构建风格。 | | --profile | 专门用于性能分析的版本。此选项目前不支持模拟器目标。 | | --release | 准备发布到 app store 的发布版本。 | | --flavor | 由特定于平台的构建设置定义的自定义应用风格。这需要在 Android Gradle 脚本和自定义 Xcode 方案中使用产品风格。 |

其他选项

参数-t--target指定应用的主入口点文件。它必须是一个包含main()方法的 Dart 文件。默认值为lib/main.dart。下面的命令使用lib/app.dart作为入口点文件。

$ flutter run -t lib/app.dart

如果您的应用有不同的路线,请使用参数--route来指定运行应用时要加载的路线。

如果你想记录正在运行的 Flutter app 的进程 id,使用参数--pid-file指定文件来写进程 id。有了进程 id,您可以发送信号SIGUSR1来触发热重装,发送信号SIGUSR2来触发热重启。在下面的命令中,进程 id 被写入文件~/app.pid

$ flutter run --pid-file ~/app.pid

现在我们可以使用kill向正在运行的 Flutter app 发送信号。

$ kill -SIGUSR1 $(<~/app.pid)
$ kill -SIGUSR2 $(<~/app.pid)

表 2-6 显示了flutter run支持的其他论点。

表 2-6

flutter run的额外参数

|

争论

|

描述

|

缺省值

| | --- | --- | --- | | --hot / --not-hot | 是否应启用热重装。 | 在 | | --build / --no-build | 在运行应用之前,是否应该构建它。 | 在 | | --pub / --no-pub | 是否先运行flutter packages get再运行。 | 在 | | --target-platform | 为 Android 设备构建应用时,指定目标平台。可能的值有defaultandroid-armandroid-arm64。 | default | | --observatory-port | 指定观察站调试器连接的端口。 | 0(随机自由港) | | --start-paused | 让应用以暂停模式启动,并等待调试器连接。 |   | | --trace-startup | 开始追踪。 |   | | --enable-software-rendering | 使用 Skia 启用渲染。 |   | | --skia-deterministic-rendering | 与--enable-software-rendering一起使用时,提供 100%确定性 Skia 渲染。 |   | | --trace-skia | 启用 Skia 代码跟踪。 |   |

图 2-11 显示运行命令flutter run的输出。从输出中,我们可以看到正在运行的 app 的天文台端口,这对于其他 Flutter SDK 命令与正在运行的 app 协同工作非常重要。我们可以通过按不同的键与控制台进行交互。例如,按“r”触发热重装。按下“h”后,flutter run会显示一条关于它可以接受的所有命令的帮助消息。

img/479501_1_En_2_Fig11_HTML.jpg

图 2-11

Flutter 运行命令的输出

2.9 构建 Flutter 应用二进制文件

问题

你想为 Android 和 iOS 平台构建应用二进制文件。

解决办法

使用命令flutter build

讨论

为了将 Flutter 应用部署到设备上并发布到应用商店,我们需要为 Android 和 iOS 平台构建二进制文件。命令flutter build支持构建这些二进制文件。

为 Android 构建 APK 文件

命令flutter build apk为你的应用构建 APK 文件。表 2-7 显示了该命令支持的参数。

表 2-7

Flutter 生成参数 apk

|

争吵

|

描述

| | --- | --- | | --debug | 构建调试版本。 | | --profile | 构建一个专门用于性能分析的版本。 | | --release | 构建发布版本,准备发布到 app store。 | | --flavor | 构建由特定于平台的构建设置定义的自定义应用风格。这需要在 Android Gradle 脚本和自定义 Xcode 方案中使用产品风格。 | | --pub / --no-pub | 构建 app 前是否运行flutter packages get。 | | --build-number = | 一个整数,用于指定递增的内部版本号。对于每个版本,该值必须是唯一的。该值被用作“versionCode”。 | | --build-name = | 格式为x.y.z的字符串版本号。该值被用作“versionName”。 | | --build-shared-library | 编译成 a∫。所以归档吧。 | | --target-platform | 目标平台。可能的值是android-armandroid-arm64。 |

建立 APK 文件时,--release是默认模式。下面的命令构建了一个发布版本,版本号为5,版本名为0.1.0

$ flutter build apk --build-number=5 --build-name=0.1.0

为 iOS 构建

命令flutter build ios构建 iOS 应用捆绑包。该命令的参数--debug--profile--release--flavor--pub--no-pub--build-number--build-versionflutter build apk相同。--build-number的值作为CFBundleVersion,而--build-name的值作为CFBundleShortVersionString

它也有其他的论点;参见表 2-8 。

表 2-8

颤动构建 ios 的额外参数

|

争吵

|

描述

| | --- | --- | | --simulator | 为 iOS 模拟器创建一个版本。 | | --no-simulator | 为 iOS 设备构建一个版本。 | | --codesign / --no-codesign | 是否对应用包进行签名。默认值为--codesign。 |

默认情况下,flutter build ios为设备构建 app,即使用--no-simulator。以下命令为模拟器构建了一个调试版本,但没有对应用包进行签名。

$ flutter build ios --debug --no-codesign --simulator

2.10 安装 Flutter 应用

问题

你想把 Flutter 应用安装到模拟器或者设备上。

解决办法

使用命令flutter install

讨论

命令flutter install将当前的 Flutter 应用安装到仿真器或设备上。要安装该应用,您需要至少启动一个模拟器或连接一个设备。在安装应用之前,目标仿真器或设备应该有一个可用的二进制文件。首先使用flutter build构建二进制文件。

以下命令安装构建的二进制文件。

$ flutter install

2.11 管理包

问题

你想要管理 Flutter 应用的依赖关系。

解决办法

使用命令flutter packages

讨论

使用包是管理项目依赖关系的捷径。Flutter 继承了相同的依赖性管理方式。你可能在其他编程平台上看到过类似的概念。为了让依赖关系管理工作,我们需要有一种方法来描述可共享的组件及其依赖关系。我们还需要一个工具来获取依赖关系。表 2-9 显示了不同平台的包管理工具。Flutter SDK 使用命令flutter packages来管理依赖关系,它使用了底层的 Dart pub工具。

表 2-9

包管理工具

|

平台

|

描述文件

|

工具

| | --- | --- | --- | | Node.js | package.json | 新公共管理故事 | | 镖摆动 | pubspec.yaml | pub``flutter packages | | 爪哇 | pom.xml``build.gradle | 专家格拉德尔 | | 红宝石 | Gemfile | 大错 |

命令flutter packages get下载 Flutter 项目中的依赖包。命令flutter packages upgrade升级一个 Flutter 项目中的包。这两个命令简单地围绕 Dart 的底层pub工具。我们也可以使用flutter packages pub直接调用 Dart pub工具。命令flutter packages不能做太多,因为它提供的功能有限。您可以随时使用flutter packages pub将任务委派给 Dart pub工具。

注意

你应该使用flutter packages getflutter packages upgrade来管理 Flutter 应用的依赖关系。不应使用 Dart pub 工具中的命令pub getpub upgrade。如果您需要 Dart pub工具的更多功能,请使用flutter packages pub

命令flutter packages testpub run test相同,但与flutter test不同。由flutter packages test运行的测试托管在一个纯 Dart 环境中,所以像dart:ui这样的库是不可用的。这使得测试运行得更快。如果您正在构建不依赖于 Flutter SDK 中任何包的库,您应该使用这个命令来运行测试。

2.12 运行 Flutter 试验

问题

您已经为 Flutter 应用编写了测试,并且您想要确保这些测试通过。

解决办法

使用命令flutter test

讨论

测试是可维护软件项目的重要组成部分。你应该对 Flutter 应用进行测试。命令flutter test运行 Flutter 应用的测试。运行该命令时,您可以提供一个以空格分隔的相对文件路径列表,以指定要运行的测试文件。如果没有提供文件,则包含test目录中文件名以_test.dart结尾的所有文件。下面的命令运行测试文件test/mytest.dart

$ flutter test test/mytest.dart

筛选要运行的测试

参数--name指定正则表达式来匹配要运行的测试的名称。一个测试文件可以包含多个测试。如果只需要做简单的子串匹配,就用--plain-name代替。以下命令显示了--name--plain-name的用法。

$ flutter test --name="smoke\d+"
$ flutter test --plain-name=smoke

您可以使用--name--plain-name指定多个匹配条件。要运行的测试需要匹配所有给定的条件。以下命令同时使用了--name--plain-name

$ flutter test --name="smoke.*" --plain-name=test

测试覆盖率

如果你想知道你的测试的覆盖范围,使用参数--coverage。测试结束后,flutter test生成测试覆盖信息并保存到文件coverage/lcov.info中。可以使用参数--coverage-path指定覆盖信息的输出路径。如果你有基本的覆盖率数据,你可以把它放入路径coverage/lcov.base.info并传递参数--merge-coverageflutter test,然后 Flutter SDK 会使用 lcov 合并这两个覆盖率文件。

要查看覆盖率报告,您需要安装 lcov。在 macOS 上,可以使用自制软件安装 lcov。

$ brew install lcov

命令genhtml从 lcov 覆盖信息文件生成 HTML 文件。以下命令生成 HTML 覆盖率报告。打开生成的文件index.html查看报告。

$ genhtml coverage/lcov.info --output-directory coverage_report

调试测试

如果你想调试一个测试文件,你可以使用参数--start-paused。这种模式下只允许一个测试文件。执行会暂停,直到连接了调试器。以下命令调试文件test/simple.dart

$ flutter test --start-paused test/simple.dart

其他选项

还有其他有用的论据;参见表 2-10 。

表 2-10

flutter test的额外参数

|

争论

|

描述

|

缺省值

| | --- | --- | --- | | --j--concurrency | 要运行的并发测试的数量。 | 6 | | --pub / --no-pub | 是否在运行测试之前运行flutter packages get。 | 在 |

2.13 分析代码

问题

您的 Flutter 代码编译成功,并且在测试中看起来不错。但是,您想知道在您的代码中是否有任何潜在的错误或不良的代码实践。

解决办法

使用命令flutter analyze

讨论

即使您的代码成功编译并通过了所有测试,代码仍有可能存在潜在的错误或不好的味道。例如,声明了一个局部变量,但从未使用过。尽可能保持代码的整洁是一个好习惯。Dart 提供了分析器来分析源代码以发现潜在的错误。

命令flutter analyze接受目录列表来扫描 Dart 文件。如果没有提供路径,flutter analyze 只分析当前工作目录。以下命令分析目录~/my_app/lib

$ flutter analyze ~/my_app/lib

分析结果可以用参数--write写入文件。默认情况下,结果会写入控制台。您还可以传递参数--watch让分析器观察文件系统的变化,并连续运行分析。

表 2-11 显示了flutter analyze的额外参数。

表 2-11

flutter analyze的额外参数

|

争论

|

描述

|

缺省值

| | --- | --- | --- | | --current-package / --no-current-package | 是否分析当前项目。如果--no-current-package被启用并且没有指定目录,那么将不进行任何分析。 | 在 | | --pub / --no-pub | 运行分析前是否运行flutter packages get。 | 在 | | --preamble / --no-preamble | 是否显示正在分析的当前文件。 | 在 | | --congratulate / --no-congratulate | 是否在没有错误、警告、提示或 lints 的情况下显示输出。 | 在 | | --watch | 持续监视文件系统的变化,并运行分析作为响应。 |   |

命令flutter analyze将代码分析委托给 Dart dartanalyzer工具。我们可以使用项目根目录中的文件analysis_options.yaml来定制分析行为。

图 2-12 显示了在代码中发现一个问题的flutter analyze的输出。

img/479501_1_En_2_Fig12_HTML.jpg

图 2-12

Flutter 分析命令的输出

2.14 管理仿真器

问题

您希望管理 Flutter SDK 使用的不同模拟器。

解决办法

使用命令flutter emulators

讨论

在为 Flutter SDK 设置 Android 和 iOS 平台时,我们还为 Android 和 iOS 创建了模拟器。对于 Android,我们可以使用 AVD 管理器来管理仿真器。对于 iOS,我们可以使用 Xcode 来管理模拟器。如果我们能以同样的方式管理 Android 模拟器和 iOS 模拟器,那将非常方便。命令flutter emulators是管理仿真器的工具。

运行flutter emulators显示所有可供 Flutter SDK 使用的仿真器;见图 2-13 。

img/479501_1_En_2_Fig13_HTML.jpg

图 2-13

指令 Flutter 模拟器的输出

要启动模拟器,使用flutter emulators --launch <emulator_id>。以下命令启动Nexus_6P_API_28模拟器。您只需要提供部分 ID 来找到要启动的模拟器。部分 ID 只能匹配一个仿真器。

$ flutter emulators --launch Nexus

我们还可以使用flutter emulators --create创建一个新的 Android 模拟器。下面的命令创建一个名为Pixel的新模拟器。此命令只能创建基于像素设备的模拟器。

$ flutter emulators --create --name Pixel

2.15 截图

问题

你想截图你正在运行的应用。

解决办法

使用命令flutter screenshot

讨论

Android 模拟器和 iOS 模拟器都提供了截图的原生功能。对于 iOS 模拟器,这可以使用菜单文件新屏幕截图来完成。对于 Android 模拟器,这可以通过点击浮动控制栏中的屏幕截图图标来完成。但是使用 UI 控件并不方便。默认情况下,模拟器拍摄的屏幕截图会保存到桌面。您必须配置模拟器以保存到所需的位置。

命令flutter screenshot比模拟器中的内置特性更容易使用。可以使用参数-o--output指定保存截图的位置;请参见以下命令。

$ flutter screenshot -o ~/myapp/screenshots/home.png

flutter screenshot可以拍摄不同类型的截图。参数--type接受表 2-12 中的值。

表 2-12

截图的类型

|

类型

|

描述

| | --- | --- | | Device | 使用设备的原生屏幕截图功能。该屏幕截图包括当前显示的整个屏幕。这是默认类型。 | | Rasterizer | 使用光栅化器渲染的 Flutter 应用的屏幕截图。 | | skia | 渲染成 Skia 图片的 Flutter app 截图。 |

对于rasterizerskia类型,需要参数--observatory-port提供运行 app 的 Dart 天文台端口号。该端口显示在命令flutter run的输出中。

2.16 附加到正在运行的应用

问题

你的 Flutter 应用不是用flutter run启动的,但是你需要想和它互动。

解决办法

使用命令flutter attach

讨论

当使用flutter run启动 Flutter 应用时,我们可以使用控制台进行交互。但是,该应用也可以通过其他方式启动。例如,我们可以关闭设备上的应用,然后再打开它。在这种情况下,我们失去了对正在运行的应用的控制。flutter attach提供了一种连接正在运行的应用的方式。

如果应用已经在运行,并且你知道它的观测站的端口,使用flutter attach --debug-port来连接它。以下命令附加到正在运行的应用。

$ flutter attach --debug-port 10010

如果没有提供观察端口,flutter attach会开始监听和扫描新激活的应用。当检测到一个新的天文台时,这个命令会自动连接到应用。

$ flutter attach

在图 2-14 中,flutter attach最初在等待一个新的 Flutter app 启动。一旦一个 Flutter 应用被启动,flutter attach连接到它并显示与flutter run相同的控制台。

img/479501_1_En_2_Fig14_HTML.jpg

图 2-14

Flutter 附着命令的输出

2.17 跟踪正在运行的 Flutter 应用

问题

你想跟踪一个正在运行的应用的执行。

解决办法

使用命令flutter trace

讨论

要开始跟踪,我们需要知道正在运行的应用的观察站端口,并用参数--debug-port将这个端口提供给flutter trace。默认情况下,跟踪运行10秒,并将结果 JSON 文件写入当前目录,文件名如trace_01.jsontrace_02.json等等。在下面的命令中,观察端口是51240

$ flutter trace --debug-port=51240

使用参数-d--duration指定跟踪运行的持续时间(秒)。以下命令运行跟踪 5 秒钟。

$ flutter trace --debug-port=51240 -d 5

如果您喜欢手动控制跟踪进度,您可以先使用flutter trace --start开始跟踪,然后在稍后使用flutter trace --stop停止跟踪。值得注意的是,调用flutter trace --stop时,跟踪需要等待--duration中指定的时间后才会停止。在下面的命令中,在第二个flutter trace --stop之后,跟踪再等待 10 秒才停止,这是--duration的默认值。

$ flutter trace --start
$ flutter trace --stop

要立即停止跟踪,请使用以下命令。

$ flutter trace --stop -d 0

2.18 配置 Flutter SDK

问题

你想配置不同设置的 Flutter SDK。

解决办法

使用命令flutter config

讨论

命令flutter config允许配置一些 Flutter SDK 设置。表 2-13 显示了flutter config的参数。

表 2-13

flutter config的参数

|

争论

|

描述

|

缺省值

| | --- | --- | --- | | --analytics / --no-analytics | 是否报告匿名工具使用统计和崩溃报告。 | 在 | | --clear-ios-signing-cert | 清除已存储的用于为 iOS 设备部署的应用签名的开发证书。 |   | | --gradle-dir | 设置 Gradle 安装目录。 |   | | --android-sdk | 设置 Android SDK 目录。 |   | | --android-studio-dir | 设置 Android Studio 安装目录。 |   |

要删除设置,只需将其配置为空字符串。以下命令禁用分析报告。

$ flutter config --no-analytics

2.19 显示应用日志

问题

您希望看到运行在模拟器或设备上的 Flutter 应用生成的日志。

解决办法

使用命令flutter logs

讨论

即使我们可以调试 Flutter 应用的代码来找出某些问题的原因,日志对于错误诊断仍然非常有价值。在 Flutter 应用中生成日志最简单的方法是调用print()方法。命令flutter logs监视设备上生成的日志,并打印到控制台。

$ flutter logs

如果您想在读取日志之前清除日志历史,请使用参数-c--clear

$ flutter logs -c

图 2-15 显示了flutter logs的输出。

img/479501_1_En_2_Fig15_HTML.jpg

图 2-15

Flutter 日志命令的输出

2.20 格式化源代码

问题

您希望确保应用的源代码遵循相同的代码风格。

解决办法

使用命令flutter format

讨论

让你的应用拥有相同的代码风格是一个很好的实践,特别是对于开发团队。一致的代码风格也有利于代码评审。命令flutter format可以格式化源代码文件,以匹配 Dart 的默认代码样式。

要运行flutter format,您需要提供一个用空格分隔的路径列表。以下命令格式化当前目录。

$ flutter format .

flutter format简单地将格式化任务委托给 Dart dartfmt工具。代码样式在镖语官方指南( https://dart.dev/guides/language/effective-dart/style )中有描述。表 2-14 显示了flutter format的额外参数。

表 2-14

Flutter 格式的额外参数

|

争吵

|

描述

| | --- | --- | | -n--dry-run | 只显示哪些文件将被修改,而不实际修改它们。 | | --set-exit-if-changed | 如果该命令改变了格式,返回退出代码1。 | | -m--machine | 将输出格式设置为 JSON。 |

2.21 列出连接的设备

问题

您希望看到所有可以被 Flutter SDK 使用的连接设备。

解决办法

使用命令flutter devices

讨论

Flutter SDK 要求在运行某些命令之前至少准备好一个仿真器或设备。Flutter SDK 使用术语“设备”来指代 Android 模拟器、iOS 模拟器和真实设备。命令flutter devices列出了 Flutter SDK 可以使用的所有设备。图 2-16 显示了flutter devices的输出。

img/479501_1_En_2_Fig16_HTML.jpg

图 2-16

Flutter 装置的输出

2.22 运行集成测试

问题

您已经使用 Flutter Driver 编写了集成测试,并且想要运行这些测试。

解决办法

使用命令flutter drive

讨论

Flutter Driver 是 Flutter SDK 提供的运行集成测试的工具。当运行集成测试时,应用本身运行在模拟器或设备上,但是测试脚本运行在您的本地机器上。在测试期间,测试脚本连接到正在运行的应用,并向应用发送命令来模拟不同的用户操作。测试脚本可以执行像点击和滚动这样的动作。它还可以读取小部件属性并验证它们的正确性。

flutter drive是运行集成测试的命令。它可以自己启动应用或连接到现有的运行应用。当flutter drive启动 app 时,它可以取与flutter run相同的参数,包括--debug--profile--flavor--route--target--observatory-port--pub--no-pub--trace-startup。这些参数与flutter run中的含义相同。连接已有 app 时,需要用已有 app 的天文台 URL 指定参数--use-existing-app;请参见以下命令。

$ flutter drive --use-existing-app=http://localhost:50124

当启动测试脚本时,flutter drive根据应用的入口点文件使用一个约定来定位测试脚本文件。使用参数--target指定入口点文件,默认值为lib/main.dartflutter drive试图在test_driver目录中找到同名但带有后缀_test.dart的测试脚本文件。例如,如果入口点文件是lib/main.dart,它试图找到测试脚本文件test_driver/main_test.dart。您可以使用参数--driver明确地指定测试脚本文件;请参见以下命令。

$ flutter drive --driver=test_driver/simple.dart

如果应用由flutter drive启动,那么应用将在测试脚本完成后停止,除非参数--keep-app-running被指定为保持运行。当连接到一个现有的应用时,应用在测试脚本完成后继续运行,除非参数--no-keep-app-running被指定来停止它。以下命令在测试后保持应用运行。

$ flutter drive --keep-app-running

2.23 启用 Flutter SDK 命令的 Bash 完成

问题

当键入 Flutter SDK 命令时,您希望为您的 shell 提供完成支持。

解决办法

使用命令flutter bash-completion设置完成。

讨论

有了 shell 完成支持,当您键入一些命令时,shell 会尝试完成它。flutter bash-completion打印设置脚本,以支持 bash 和 zsh 的完成。如果没有提供参数,安装脚本将被打印到控制台。如果提供了文件路径,安装脚本将被写入该文件。

在 macOS 上,我们可以先用自制软件安装bash-completion

$ brew install bash-completion

如果您正在使用 bash,您需要修改文件~/.bash_profile来添加下面一行。

[ -f /usr/local/etc/bash_completion ] && . /usr/local/etc/bash_completion

然后可以运行flutter bash-completion将设置脚本保存到目录/usr/local/etc/bash_completion.d;请参见以下命令。

$ flutter bash-completion /usr/local/etc/bash_completion.d/flutter

最后,您应该运行source ~/.bash_profile或重启 shell 来实现完成。

如果您正在使用 zsh,您可以将设置脚本添加到文件~/.zshrc中。首先你需要在~/.zshrc的顶部添加下面一行。

autoload bashcompinit
bashcompinit

然后您需要运行下面的命令来将设置脚本添加到~/.zshrc

$ flutter bash-completion >> ~/.zshrc

最后,您应该运行source ~/.zshrc或重启 shell 来实现完成。

2.24 清理 Flutter 应用的构建文件

问题

你想要清理 Flutter 应用的构建文件。

解决办法

使用命令flutter clean

讨论

命令flutter clean删除build目录中的文件。build目录的磁盘空间可能很大,即使对于小应用也是如此。比如搭建好 Flutter sample app 后,build目录的大小大概是 200M。学习 Flutter 的时候,可能会创建很多小 app 进行测试。当你认为你已经使用完这些应用时,运行flutter clean是个好主意。你会发现你可以回收大量的磁盘空间。

2.25 管理 Flutter SDK 缓存

问题

你想要显式地管理 Flutter SDK 的缓存。

解决办法

使用命令flutter precache

讨论

Flutter SDK 在bin/cache目录中保存了所需工件的缓存。该目录包含 Dart SDK、Flutter 引擎、材质字体和 Gradle wrapper 的二进制文件。如果该缓存不存在,则会自动填充。命令flutter precache显式更新缓存。除了configprecachebash-completionupgrade命令之外,大多数 Flutter 命令在执行前都会自动更新缓存,所以大多数时候你不需要显式运行这个命令。

e 有参数-a--all-platforms来指定是否应该下载所有平台的工件。默认情况下,只下载当前平台的工件。

$ flutter precache -a

2.26 摘要

这一章是关于你在开发 Flutter 应用时可能需要用到的工具。您可能不需要使用所有这些工具。在 ide 的帮助下,您可以执行 ide 中的大多数操作。这些工具的知识仍然很有价值,因为您可以使用这些工具做更多的事情。在下一章中,我们将看到关于 Dart 语言基本部分的配方。

三、基本 Dart

Flutter 项目可以有跨平台代码和特定于平台的代码。跨平台代码是用 Dart 写的。充分了解 Dart 是构建 Flutter 应用的先决条件。Dart 语言的细节超出了本书的范围。您可以找到大量与 Dart 相关的在线资源。然而,涵盖 Dart 的基本部分对于构建 Flutter 应用还是很有帮助的。本章中的食谱涵盖了 Dart 的不同方面。如果您对 Dart 知识有信心,可以跳过这一章。

3.1 了解内置类型

问题

你想知道 Dart 的内置类型。

解决办法

Dart 有内置的数字、字符串、布尔值、列表、地图、符文和符号类型。

讨论

Dart 有几个内置类型,包括数字、字符串、布尔值、列表、地图、符文和符号。

民数记

Dart 中的数字可以是不大于 64 位的整数值或 IEEE 754 标准指定的 64 位双精度浮点数。类型intdouble分别代表这两种类型的数字。类型numintdouble的超类型。与 Java 中的原始类型不同,Dart 中的数字也是对象。他们有办法和他们一起工作。

在清单 3-1 中,x的类型是int,而y的类型是double。方法toRadixString()通过将值转换成指定的基数返回一个字符串值。方法toStringAsFixed()确保给定的小数位数保存在字符串表示中。double的静态方法tryParse()试图将字符串解析为double文字。

var x = 10;
var y = 1.5;
assert(x.toRadixString(8) == '12');
assert(y.toStringAsFixed(2) == '1.50');
var z = double.tryParse('3.14');
assert(z == 3.14);

Listing 3-1
Numbers

用线串

Dart 字符串是 UTF-16 代码单元的序列。单引号或双引号都可以用来创建字符串。用哪个引语并不重要。关键是要在整个代码库中保持一致。Dart 内置了对字符串插值的支持。可以使用形式${expression}将表达式嵌入字符串。使用字符串时,会计算嵌入表达式的值。如果表达式是一个标识符,那么{}可以省略。在清单 3-2 中,name是一个标识符,所以我们可以在字符串中使用$name

var name = 'Alex';
assert('The length of $name is ${name.length}' == 'The length of Alex is 4');

Listing 3-2String interpolation

如果您想要连接字符串,您可以简单地将这些字符串文字相邻放置,而不使用+操作符;参见清单 3-3 。

var longString = 'This is a long'
  'long'
  'long'
  'string';

Listing 3-3String concatenation

创建多行字符串的另一种方法是使用带单引号或双引号的三重引号;参见清单 3-4 。

var longString2 = "'
This is also a long
  long
  long
  string
"';

Listing 3-4Multi-line string

布尔运算

使用类型bool表示布尔值。bool类型只有两个对象:truefalse。值得注意的是,ifwhileassert中只能使用bool值作为检查条件。JavaScript 有更广泛的真值和假值的概念,而 Dart 遵循更严格的规则。例如,if ('abc')在 JavaScript 中有效,但在 Dart 中无效。

在清单 3-5 中,name是一个空字符串。为了在if中使用它,我们需要调用 getter isEmpty。我们还需要对null0进行显式检查。

var name = ";
if (name.isEmpty) {
  print('name is emtpy');
}
var value;
assert(value == null);

var count = 5;
while(count-- != 0) {
  print(count);
}

Listing 3-5Booleans

列表和地图

列表和地图是常用的集合类型。在 Dart 中,数组是List对象。可以使用文字或构造函数创建列表和映射。建议尽可能使用集合文字。清单 3-6 展示了如何使用文字和构造函数创建列表和映射。

var list1 = [1, 2, 3];
var list2 = List<int>(3);
var map1 = {'a': 'A', 'b': 'B'};
var map2 = Map<String, String>();

Listing 3-6
Lists and maps

符文

符文是字符串的 UTF-32 代码点。要在字符串中表示 32 位 Unicode 值,我们可以使用形式\uXXXX,其中XXXX是码位的四位十六进制值。如果码位不能用四位十六进制值表示,那么需要用{}将这些数字换行,例如\u{XXXXX}。在清单 3-7 中,字符串值包含两个表情符号。

var value = '\u{1F686} \u{1F6B4}';
print(value);

Listing 3-7
Runes

标志

符号对象代表一个运算符或标识符。可以使用构造函数Symbol(<name>)或符号文字# 创建符号。用相同名称创建的符号是相等的;见清单3-8T3。当您希望按名称引用标识符时,应该使用符号。

assert(Symbol('a') == #a);

Listing 3-8
Symbols

3.2 使用枚举类型

问题

您希望有一种类型安全的方法来声明一组常数值。

解决办法

使用枚举类型。

讨论

像其他编程语言一样,Dart 也有枚举类型。要声明枚举类型,请使用enum关键字。枚举中的每个值都有一个index getter 来获取该值从零开始的位置。使用values获得一个枚举中所有值的列表。枚举通常用在switch语句中。在清单 3-9 中,枚举类型TrafficColor有三个值。第一个值red的索引是0

enum TrafficColor { red, green, yellow }

void main() {
  assert(TrafficColor.red.index == 0);
  assert(TrafficColor.values.length == 3);

  var color = TrafficColor.red;
  switch (color) {
    case TrafficColor.red:
      print('stop');
      break;
    case TrafficColor.green:
      print('go');
      break;
    case TrafficColor.yellow:
      print('be careful');
  }
}

Listing 3-9Enumerated type

3.3 使用动态类型

问题

你不知道对象的类型或者你不关心类型。

解决办法

使用dynamic类型。

讨论

Dart 是一种强类型语言。大多数时候,我们希望一个对象有一个定义好的类型。但是,有时候我们可能不知道或者不关心实际的类型;我们可以用dynamic作为类型。动态类型经常与Object类型混淆。Objectdynamic都允许所有值。如果你想声明所有的对象都被接受,应该使用Object。如果类型是dynamic,我们可以使用is运算符来检查它是否是想要的类型。使用runtimeType可以检索实际类型。在清单 3-10 中,value的实际类型是int,然后类型改为String

dynamic value = 1;
print(value.runtimeType);
value = 'test';
if (value is String) {
  print('string');
}

Listing 3-10Use dynamic type

3.4 了解功能

问题

您希望了解 Dart 中的函数。

解决办法

Dart 中的函数非常强大和灵活。

讨论

Dart 中的函数是对象,类型为Function。函数可以赋值,传入函数参数,并用作函数返回值。在 Dart 中创建高阶函数非常容易。一个函数可以有零个或多个参数。有些参数是必需的,有些是可选的。必需的参数首先出现在参数列表中,后面是可选参数。可选的位置参数包含在[]中。

当一个函数有一长串参数时,很难记住这些参数的位置和意义。最好使用命名参数。使用@required注释可以根据需要标记命名参数。参数可以使用=指定默认值。如果没有提供默认值,则默认值为null

在清单 3-11 中,函数sum()有一个可选的位置参数initial,默认值为0。函数joinToString()有一个必需的命名参数separator和两个可选的命名参数prefixsuffixjoinToString()中使用的箭头语法是只有一个表达式的函数体的简写。语法=> expr{ return expr; }相同。使用箭头语法使代码更短,更容易阅读。

import 'package:meta/meta.dart';

int sum(List<int> list, [int initial = 0]) {
  var total = initial;
  list.forEach((v) => total += v);
  return total;
}

String joinToString(List<String> list,
        {@required String separator, String prefix = ", String suffix = "}) =>
    '$prefix${list.join(separator)}$suffix';

void main() {
  assert(sum([1, 2, 3]) == 6);
  assert(sum([1, 2, 3], 10) == 16);

  assert(joinToString(['a', 'b', 'c'], separator: ',') == 'a,b,c');
  assert(
      joinToString(['a', 'b', 'c'], separator: '-', prefix: '*', suffix: '?') ==
          '*a-b-c?');
}

Listing 3-11Function parameters

有时候你可能不需要一个函数的名字。这些匿名函数在提供回调时非常有用。在清单 3-12 中,一个匿名函数被传递给方法forEach()

var list = [1, 2, 3];
list.forEach((v) => print(v * 10));

Listing 3-12Anonymous functions

3.5 使用 Typedefs

问题

你想要一个函数类型的别名。

解决办法

使用 typedefs。

讨论

在 Dart 中,函数是对象。函数是类型Function的实例。但是函数的实际类型是由它的参数类型和返回值类型定义的。当函数用作参数或返回值时,重要的是实际的函数类型。typedef在 Dart 中允许我们创建一个函数类型的别名。类型别名可以像其他类型一样使用。在清单 3-13 ,Processor<T>是函数类型的别名,它有一个T类型的参数和一个void类型的返回。该类型用作函数process()中的参数类型。

typedef Processor<T> = void Function(T value);

void process<T>(List<T> list, Processor<T> processor) {
  list.forEach((item) {
    print('processing $item');
    processor(item);
    print('processed $item');
  });
}

void main() {
  process([1, 2, 3], print);
}

Listing 3-13typedef

3.6 使用级联运算符

问题

您希望对同一对象进行一系列操作。

解决办法

在 Dart 中使用级联运算符(..)。

讨论

Dart 有一个特殊的级联操作符(..),允许我们对同一个对象进行一系列操作。为了在其他编程语言中对同一对象进行链式操作,我们通常需要创建一个 fluent API,其中每个方法都返回当前对象。Dart 中的 cascade 操作符使这一要求变得不必要。即使方法不返回当前对象,它们仍然可以被链接。级联运算符也支持字段访问。在清单 3-14 中,级联运算符用于访问类UserAddress中的字段和方法。

class User {
  String name, email;
  Address address;

  void sayHi() => print('hi, $name');
}

class Address {
  String street, suburb, zipCode;
  void log() => print('Address: $street');
}

void main() {
  User()
    ..name = 'Alex'
    ..email = 'alex@example.org'
    ..address = (Address()
      ..street = 'my street'
      ..suburb = 'my suburb'
      ..zipCode = '1000'
      ..log())
    ..sayHi();
}

Listing 3-14Using cascade operator

3.7 覆盖运算符

问题

您希望覆盖 Dart 中的运算符。

解决办法

为运算符定义类中的重写方法。

讨论

Dart 有许多操作员。只能覆盖这些运算符的子集。这些可覆盖的操作符是<+|[]>/^[]=<=~/&~>=*<<==-%>>。对于某些类,使用运算符比使用方法更简洁。例如,List类覆盖了列表连接的+操作符。代码[1] + [2]非常容易理解。在清单 3-15 中,类Rectangle覆盖了操作符<>来按区域比较实例。

class Rectangle {
  int width, height;
  Rectangle(this.width, this.height);

  get area => width * height;

  bool operator <(Rectangle rect) => area < rect.area;
  bool operator >(Rectangle rect) => area > rect.area;
}

void main() {
  var rect1 = Rectangle(100, 100);
  var rect2 = Rectangle(200, 150);
  assert(rect1 < rect2);
  assert(rect2 > rect1);
}

Listing 3-15Overriding operators

3.8 使用构造函数

问题

您希望创建 Dart 类的新实例。

解决办法

使用构造函数。

讨论

和其他编程语言一样,Dart 中的对象是由构造函数创建的。通常,构造函数是通过声明与其类同名的函数来创建的。构造函数可以有参数来提供初始化新对象所需的值。如果没有为类声明构造函数,则提供不带参数的默认构造函数。这个默认构造函数只是调用超类中的无参数构造函数。但是,如果声明了构造函数,这个默认的构造函数就不存在。

一个类可以有多个构造函数。您可以以ClassName.identifier的形式命名这些构造函数,以便更好地阐明含义。

在清单 3-16 中,类Rectangle有一个带四个参数的常规构造函数。它还有一个命名的构造函数Rectangle.fromPosition

class Rectangle {
  final num top, left, width, height;

  Rectangle(this.top, this.left, this.width, this.height);

Rectangle.fromPosition(this.top, this.left, num bottom, num right)
      : assert(right > left),
        assert(bottom > top),
        width = right - left,
        height = bottom - top;

  @override
  String toString() {
    return 'Rectangle{top: $top, left: $left, width: $width, height: $height}';
  }
}

void main(List<String> args) {
  var rect1 = Rectangle(100, 100, 300, 200);
  var rect2 = Rectangle.fromPosition(100, 100, 300, 200);
  print(rect1);
  print(rect2);
}

Listing 3-16
Constructors

使用工厂创建对象是很常见的。Dart 有一种特殊的factory构造函数来实现这种模式。工厂构造函数并不总是返回一个类的新实例。它可能返回缓存的实例,或者子类型的实例。在清单 3-17 中,类ExpensiveObject有一个命名的构造函数ExpensiveObject._create()来实际创建一个新实例。工厂构造函数只在_instancenull时调用ExpensiveObject._create()。运行代码时,可以看到消息“created”只打印了一次。

class ExpensiveObject {
  static ExpensiveObject _instance;
  ExpensiveObject._create() {
    print('created');
  }

  factory ExpensiveObject() {
    if (_instance == null) {
      _instance = ExpensiveObject._create();
    }
    return _instance;
  }
}

void main() {
  ExpensiveObject();
  ExpensiveObject();
}

Listing 3-17Facto+ry constructor

3.9 扩展类

问题

您希望从现有的类中继承行为。

解决办法

从现有类扩展以创建子类。

讨论

Dart 是一种面向对象的编程语言。它提供了对继承的支持。一个类可以使用关键字extends从一个超类扩展而来。超类可以在子类中称为super。子类可以覆盖超类的实例方法、getters 和 setters。重写成员应该用@override注释进行注释。

抽象类是使用abstract修饰符定义的。抽象类不能被实例化。抽象类中的抽象方法没有实现,必须由非抽象子类实现。

在清单 3-18 中,类Shape是用抽象方法area()抽象的。类RectangleCircle都从Shape扩展而来,并实现了抽象方法area()

import 'dart:math' show pi;

abstract class Shape {
  double area();
}

class Rectangle extends Shape {
  double width, height;
  Rectangle(this.width, this.height);

  @override
  double area() {
    return width * height;
  }
}

class Square extends Rectangle {
  Square(double width) : super(width, width);
}

class Circle extends Shape {
  double radius;
  Circle(this.radius);

  @override
  double area() {
    return pi * radius * radius;
  }
}

void main() {
  var rect = Rectangle(100, 50);
  var square = Square(50);
  var circle = Circle(50);
  print(rect.area());
  print(square.area());
  print(circle.area());
}

Listing 3-18Inheritance

3.10 向类中添加功能

问题

您希望重用一个类的代码,但受到 Dart 的单一继承的限制。

解决办法

使用 mixins。

讨论

继承是重用代码的一种常见方式。Dart 只支持单一继承,即一个类最多只能有一个超类。如果你想重用来自多个类的代码,应该使用 mixins。一个类可以使用关键字with声明多个 mixins。mixin 是一个从Object扩展的类,并在构造函数上声明。可以使用class将 mixin 声明为常规类,或者使用mixin将其声明为专用 mixin。在清单 3-19 中,CardHolderSystemUser是混合。类AssistantStudent扩展而来,有 mixin SystemUser,所以我们可以使用Assistant实例的useSystem()方法。

class Person {
  String name;

  Person(this.name);
}

class Student extends Person with CardHolder {
  Student(String name) : super('Student: $name') {
    holder = this;
  }
}

class Teacher extends Person with CardHolder {
  Teacher(String name) : super('Teacher: $name') {
    holder = this;
  }
}

mixin CardHolder {
  Person holder;

  void swipeCard() {
    print('${holder.name} swiped the card');
  }
}

mixin SystemUser {
  Person user;

  void useSystem() {
    print('${user.name} used the system.');
  }
}

class Assistant extends Student with SystemUser {
  Assistant(String name) : super(name) {
    user = this;
  }

}

void main() {
  var assistant = Assistant('Alex');
  assistant.swipeCard();
  assistant.useSystem();
}

Listing 3-19Mixins

3.11 使用接口

问题

你想有一个契约让课程遵循。

解决办法

使用类的隐式接口。

讨论

您应该熟悉作为类契约的接口。与其他面向对象的编程语言不同,Dart 没有接口的概念。每个类都有一个隐式接口,包含该类的所有实例成员及其实现的接口。你可以使用implements来声明一个类实现了另一个类的 API。在清单 3-20 中,类CachedDataLoader实现了类DataLoader的隐式接口。

class DataLoader {
  void load() {
    print('load data');
  }
}

class CachedDataLoader implements DataLoader {
  @override
  void load() {
    print('load from cache');
  }
}

void main() {
  var loader = CachedDataLoader();
  loader.load();
}

Listing 3-20
Interfaces

3.12 使用泛型

问题

当您的代码被设计为使用不同的类型时,您希望具有类型安全。

解决办法

使用泛型类和泛型方法。

讨论

对于开发人员来说,泛型并不是一个陌生的概念,尤其是对于 Java 和 C#开发人员来说。使用泛型,我们可以向类和方法添加类型参数。泛型通常在集合中用于创建类型安全的集合。清单 3-21 显示了通用集合在 Dart 中的用法。Dart 泛型类型被具体化,这意味着类型信息在运行时可用。这就是为什么names的类型是List<String>的原因。

var names = <String>['a', 'b', 'c'];
print(names is List<String>);
var values = <String, int>{'a': 1, 'b': 2, 'c': 3};
print(values.values.toList());

Listing 3-21Generic collections

我们可以使用泛型来创建处理不同类型的类。在清单 3-22 中,Pair<F, S>是一个泛型类,有两个类型参数FS。使用extends指定泛型类型参数的上限。CardHolder中的类型参数P有一个类型Person的上界,所以CardHolder<Student>有效。

class Pair<F, S> {
  F first;
  S second;

  Pair(this.first, this.second);
}

class Person {}

class Teacher extends Person {}

class Student extends Person {}

class CardHolder<P extends Person> {
  P holder;
  CardHolder(this.holder);
}

void main() {
  var pair = Pair('a', 1);
  print(pair.first);
  var student = Student();
  var cardHolder = CardHolder(student);
  print(cardHolder is CardHolder<Student>);
  print(cardHolder);
}

Listing 3-22Generic types

泛型方法可以添加到常规类中。在清单 3-23 中,常规类Calculator有两个泛型方法addsubtract

class Calculator {
  T add<T extends num>(T v1, T v2) => v1 + v2;
  T subtract<T extends num>(T v1, T v2) => v1 - v2;
}

void main() {
  var calculator = Calculator();
  int r1 = calculator.add(1, 2);
  double r2 = calculator.subtract(0.1, 0.2);
  print(r1);
  print(r2);
}

Listing 3-23Generic methods

3.13 使用库

问题

您希望重用 Dart SDK 或社区中的库。

解决办法

使用import导入库以在您的应用中使用它们。

讨论

在开发重要的 Dart 应用时,不可避免地要使用库。这些库可以是 Dart SDK 中的内置库,也可以是社区贡献的库。要使用这些库,我们需要先用import导入它们。import只有一个参数来指定库的 URI。内置库有 URI 方案dart:,比如dart:htmldart:convert。社区包有 URI 方案package:,由 Dart pub工具管理。清单 3-24 展示了导入库的例子。

import 'dart:html';
import 'package:meta/meta.dart';

Listing 3-24Import libraries

两个库可能导出相同的标识符。为了避免冲突,我们可以使用as为其中一个库或者两个库提供前缀。在清单 3-25 中,lib1.dartlib2.dart都导出了类Counter。在给这两个库分配不同的前缀后,我们可以使用前缀来访问类Counter

import 'lib1.dart' as lib1;
import 'lib2.dart' as lib2;

lib1.Counter counter;

Listing 3-25Rename libraries

您不需要导入库的所有成员。使用show显式包含成员。使用hide显式排除成员。在清单 3-26 中,导入库dart:math时,只导入Random;导入库dart:html时,只排除Element

import 'dart:math' show Random;
import 'dart:html' hide Element;

Listing 3-26Show and hide members

3.14 使用异常

问题

您希望处理 Dart 应用中的故障。

解决办法

使用throw报告故障。使用try-catch-finally处理异常。

讨论

代码失败。代码报告失败并处理它们是很自然的事情。Dart 的异常机制与 Java 类似,只是 Dart 中的所有异常都是未检查的异常。Dart 中的方法不声明它们可能抛出的异常,所以不需要捕捉异常。但是,未捕获的异常会导致隔离挂起,并可能导致程序终止。正确的故障处理也是健壮应用的一个关键特征。

报告故障

我们可以使用throw来抛出异常。事实上,所有非null对象都可以被抛出,不仅仅是实现类型ErrorException的类型。建议只投掷ErrorException类型的物体。

一个Error对象代表代码中不应该发生的 bug。例如,如果一个列表只包含三个元素,试图访问第四个元素会导致抛出一个RangeError。与异常不同,错误不是用来被捕获的。当错误发生时,最安全的方法是终止程序。Error它们携带着关于为什么会发生的清晰信息。

Error s 相比,Exception s 被设计为以编程方式被捕获和处理。例如,发送 HTTP 请求可能不会成功,因此我们需要在代码中处理异常来处理失败。Exception s 通常携带关于失败的有用数据。我们应该创建从Exception扩展的自定义类型来封装必要的数据。

捕捉异常

当抛出异常时,您可以捕捉它以阻止它传播,除非您重新抛出它。捕捉异常的目标是处理它。如果不想处理异常,就不应该捕捉它。使用trycatchon捕获异常。如果不需要访问异常对象,使用on就足够了。使用catch,您可以访问异常对象和堆栈跟踪。使用on指定要捕获的异常类型。

当你捕捉到一个异常时,你应该处理它。但是,有时您可能只想部分处理它。在这种情况下,您应该使用rethrow来重新抛出异常。捕捉异常但不完全处理它是一种糟糕的做法。

如果您希望无论是否抛出异常都运行一些代码,您可以将代码放在一个finally子句中。如果没有抛出异常,finally子句在try块之后运行。如果抛出异常,finally子句在匹配的catch子句之后运行。

在清单 3-27 中,函数getNumber()抛出一个自定义异常类型ValueTooLargeException。在函数main()中,异常被捕获并再次抛出。

import 'dart:math' show Random;

var random = Random();

class ValueTooLargeException implements Exception {
  int value;
  ValueTooLargeException(this.value);

  @override
  String toString() {
    return 'ValueTooLargeException{value: $value}';
  }
}

int getNumber() {
  var value = random.nextInt(10);
  if (value > 5) {
    throw ValueTooLargeException(value);
  }
  return value;
}

void main() {
  try {
    print(getNumber());
  } on ValueTooLargeException catch (e) {
    print(e);
    rethrow;
  } finally {
    print('in finally');
  }
}

Listing 3-27Use exceptions

3.15 摘要

学习一门新的编程语言不是一件容易的事情。尽管 Dart 看起来与其他编程语言相似,但 Dart 仍然有一些独特的功能。本章仅简要介绍 Dart 中的重要功能。