Flutter Packages 的開發和提交

外掛 API 現已支援 聯合外掛,從而分離在不同平臺上的實現。你可以指定 哪些平台有外掛 支援,例如 Web 和 macOS。

舊的外掛 API 會在將來被廢棄。如果在短期內你仍在使用舊版本的外掛 API,你會看到警告。想了解更多關於升級 Android 外掛的內容,請閱讀 支援新的 Android 外掛 API

Package 介紹

透過使用 package(的模式)可以建立易於共享的模組化程式碼。一個最基本的 package 由以下內容構成:

pubspec.yaml 檔案
用於定義 package 名稱、版本號、作者等其他資訊的元資料檔案。

lib 目錄
包含共享程式碼的 lib 目錄,其中至少包含一個 <package-name>.dart 檔案。

Package 類別

Package 包含以下幾種類別:

純 Dart 庫 (Dart packages)
用 Dart 編寫的傳統 package,比如 path。其中一些可能包含 Flutter 的特定功能,因此依賴於 Flutter 框架,其使用範圍僅限於 Flutter,比如 fluro

原生外掛 (Plugin packages)
使用 Dart 編寫的,按需使用 Java 或 Kotlin、Objective-C 或 Swift 分別在 Android 和/或 iOS 平台實現的 package。

外掛 package 可以針對 Android(使用 Kotlin 或 Java)、 iOS(使用 Swift 或 Objective-C)、Web、macOS、Windows 或 Linux,又或者它們的各種組合方式,進行編寫。

A concrete example is the url_launcher plugin package. To see how to use the url_launcher package, and how it was extended to implement support for web, see the Medium article by Harry Terkelsen, How to Write a Flutter Web Plugin, Part 1. 一個較為具體的實現例子是 url_launcher 外掛 package。想了解如何使用 url_launcher package,以及它如何擴充 Web 的實現,請閱讀 Medium 上由 Harry Terkelsen 撰寫的文章 如何編寫 Flutter Web 外掛,第一部分

FFI 外掛
用 Dart 語言編寫針對一個或多個特定平台的 API,使用 Dart FFI (AndroidiOSmacOS)。

開發純 Dart 的 packages

下面會為你介紹如何寫 Flutter package。

第一步:建立 package

想要建立初始的 Flutter package,請使用帶有 --template=package 標誌的 flutter create 命令:

$ flutter create --template=package hello

這將在 hello 目錄下建立一個 package 專案,其中包含以下內容:

LICENSE 檔案
大機率會是空的一個許可證檔案。

test/hello_test.dart 檔案
Package 的 單元測試 檔案。

hello.iml 檔案
由 IntelliJ 產生的配置檔案。

.gitignore 檔案
告訴 Git 系統應該隱藏哪些檔案或資料夾的一個隱藏檔案。

.metadata 檔案
IDE 用來記錄某個 Flutter 專案屬性的的隱藏檔案。

pubspec.yaml 檔案
pub 工具需要使用的,包含 package 依賴的 yaml 格式的檔案。

README.md 檔案
起步文件,用於描述 package。

lib/hello.dart 檔案
package 的 Dart 實現程式碼。

.idea/modules.xml.idea/workspace.xml 檔案
IntelliJ 的各自配置檔案(包含在 .idea 隱藏資料夾下)。

CHANGELOG.md 檔案
又一個大機率為空的文件,用於記錄 package 的版本變更。

第二步:實現 package

對於純 Dart 庫的 package,只要在 lib/<package name>.dart 檔案中新增功能實現,或在 lib 目錄中的多個檔案中新增功能實現。

如果要對 package 進行測試,在 test 目錄下新增 單元測試

關於如何組織 package 內容的更多詳細資訊,請參考 Dart library package 文件。

開發原生外掛型別的 packages

如果想要開發一個呼叫特定平台 API 的 package,你需要開發一個原生外掛 packgae。

它的 API 透過 平台通道 連線到平台特定的實現。

聯合外掛

Federated plugins (聯合外掛) 是一種將對不同平台的支援分為單獨的軟體套件。所以,聯合外掛能夠使用針對 iOS、Android、Web 甚至是針對汽車 (例如在 IoT 裝置上)分別使用對應的 package。除了這些好處之外,它還能夠讓領域專家在他們最瞭解的平臺上擴充現有平台外掛。

聯合外掛需要以下 package:

面向應用的 package
該 package 是使用者使用外掛的的直接依賴。它指定了 Flutter 應用使用的 API。

平台 package
一個或多個包含特定平台程式碼的 package。面向應用的 package 會呼叫這些平台 package—— 除非它們帶有一些終端使用者需要的特殊平台功能,否則它們不會包含在應用中。

平台介面 package
將面向應用的 package 與平台 package 進行整合的 package。該 package 會宣告平台 package 需要實現的介面,供面向應用的 package 使用。使用單一的平台介面 package 可以確保所有平台 package 都按照各自的方法實現了統一要求的功能。

整合的聯合外掛

理想情況下,當你在為一個聯合外掛新增某個平台的實現時,你會與 package 的作者合作,將你的實現納入 package。

假設你開發了 foobar_windows 外掛,用於對應 foobar 外掛的實現。在整合的聯合外掛裡,foobar 的原作者會將你的 Windows 實現作為依賴新增在 pubspec 檔案中,供面向應用的 package 呼叫。而後在開發者使用 foobar 外掛時,Windows 及已包含的其他平台的實現就自動可用了。

未整合的聯合外掛

如果你的實現出於某些原因無法被原作者整合,那麼你的外掛屬於 未整合 的聯合外掛。開發者仍然可以使用你的實現,但是必須手動在 pubspec 檔案裡新增參考。意味著開發者需要同時參考 foobar foobar_windows 依賴,才能使用對應平台的完整功能。

有關聯合外掛的更多資訊、它為什麼非常強大,以及如何實現聯合外掛,你可以閱讀 Harry Terkelsen 在 Medium 撰寫的 如何撰寫 Flutter Web 外掛,第 2 部分

指定一個外掛支援的平台

外掛可以透過向 pubspec.yaml 中的 platforms map 新增 keys 來指定其支援的平台。例如,以下是 hello 外掛的 flutter: map,它僅支援 Android 和 iOS:

flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin

environment:
  sdk: ">=2.1.0 <3.0.0"
  # Flutter versions prior to 1.12 did not support the
  # flutter.plugin.platforms map.
  flutter: ">=1.12.0"

當為更多平臺新增外掛實現時,應相應地更新 platforms map,例如這是支援 Android、iOS、macOS 和 web 的 hello 外掛的 map:

flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
      macos:
        pluginClass: HelloPlugin
      web:
        pluginClass: HelloPlugin
        fileName: hello_web.dart

environment:
  sdk: ">=2.1.0 <3.0.0"
  # Flutter versions prior to 1.12 did not support the
  # flutter.plugin.platforms map.
  flutter: ">=1.12.0"

聯合平台 package

平台 package 有著同樣的格式,但會包含 implements 入口,用於指明 package 實現的平台。例如,實現了 hello package 的 Windows 平台的 hello_windows 外掛,會在 flutter: 對映下包含以下內容:

flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        pluginClass: HelloPlugin

認可的實現

提供給 App 專案使用的 package 可以透過在 platform: 對映下宣告 default_package,認可一個平台實現外掛。如果 hello 外掛認可了 hello_windows,它看起來會是這樣:

flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
      windows:
        default_package: hello_windows

dependencies:
  hello_windows: ^1.0.0

注意如上所示,面向 App 專案的 package 可能已經包含了某些平台的實現,同時也有認可的其他平台的實現。

第一步:建立 package

想要建立原生外掛 package,請使用帶有 --template=plugin 標誌的 flutter create 命令。

你可以使用 --platforms= 命令列選項指定外掛支援的平台,後面引數是用逗號分隔的列表。可用的平台有:androidiosweblinuxmacoswindows。如果沒有指定平台,則產生的專案不支援任何平台。

使用 --org 選項,以反向域名錶示法來指定你的組織。該值用於產生的 Android 及 iOS 程式碼。

使用 -a 選項指定 Android 的語言,或使用 -i 選項指定 iOS 的語言。請選擇以下 任一項

$ flutter create --org com.example --template=plugin --platforms=android,ios -a kotlin hello
$ flutter create --org com.example --template=plugin --platforms=android,ios -a java hello
$ flutter create --org com.example --template=plugin --platforms=android,ios -i objc hello
$ flutter create --org com.example --template=plugin --platforms=android,ios -i swift hello

這將在 hello 目錄下建立一個外掛專案,其中包含以下內容:

lib/hello.dart 檔案
Dart 外掛 API 實現。

android/src/main/java/com/example/hello/HelloPlugin.kt 檔案
Android 平台原生外掛 API 實現(使用 Kotlin 程式語言)。

ios/Classes/HelloPlugin.m 檔案
iOS 平台原生外掛 API 實現(使用 Objective-C 程式語言)。

example/ 檔案
一個依賴於該外掛並說明了如何使用它的 Flutter 應用。

預設情況下,外掛專案中 iOS 程式碼使用 Swift 編寫, Android 程式碼使用 Kotlin 編寫。如果你更喜歡 Objective-C 或 Java,你可以透過 -i 指定 iOS 所使用的語言和/或使用-a 指定 Android 所使用的語言。比如:

$ flutter create --template=plugin --platforms=android,ios -i objc hello
$ flutter create --template=plugin --platforms=android,ios -a java hello

第二步:實現 package

由於原生外掛型別的 package 包含了使用多種程式語言編寫的多個平台程式碼,因此需要一些特定步驟來保證體驗的流暢性。

步驟 2a:定義 package API(.dart)

原生外掛型別 package 的 API 在 Dart 程式碼中要首先定義好,使用你鍾愛的 Flutter 編輯器,開啟 hello 主目錄,並找到 lib/hello.dart 檔案。

步驟 2b:新增 Android 平台程式碼(.kt/.java)

我們建議你使用 Android Studio 來編輯 Android 程式碼。

接下來進行如下步驟:

  1. 啟動 Android Studio;

  2. 在 Android Studio 的歡迎選單 (Welcome to Android Studio) 對話方塊中選擇開啟現有的 Android Studio 專案 (Open an existing Android Studio Project),或在選單中選擇 File > Open,然後選擇 hello/example/android/build.gradle 檔案;

  3. Gradle Sync 對話方塊中,選擇 OK

  4. 在“Android Gradle Plugin Update”對話方塊中,選擇“Don’t remind me again for this project”。

外掛中與 Android 系統徐相關的程式碼在 hello/java/com.example.hello/HelloPlugin 這個檔案裡。

你可以在 Android Studio 中點選執行 ▶ 按鈕來執行範例程式。

步驟 2c:新增 iOS 平台程式碼(.swift/.h+.m)

我們建議你使用 Xcode 來編輯 iOS 程式碼。

使用 Xcode 編輯 iOS 平台程式碼之前,首先確保程式碼至少被建構過一次(即從 IDE/編輯器執行範例程式,或在終端中執行以下命令: cd hello/example; flutter build ios --no-codesign)。

接下來執行下面步驟:

  1. 啟動 Xcode

  2. 選擇“File > Open”,然後選擇 hello/example/ios/Runner.xcworkspace 檔案。

外掛的 iOS 平台程式碼位於專案導航中的這個位置: Pods/Development Pods/hello/../../example/ios/.symlinks/plugins/hello/ios/Classes

你可以點選執行 ▶ 按鈕來執行範例程式。

步驟 2d:關聯 API 和平台程式碼

最後,你需要將 Dart 編寫的 API 程式碼與特定平台的實現相互關聯。這是透過 平台通道 完成的。

為現有的外掛專案加入平台的支援

要在現有的外掛專案中新增對特定平台的支援,請在專案目錄執行 flutter create 命令,並加入 --template=plugin。例如,要對現有的外掛專案新增 Web 支援,請執行以下命令。

$ flutter create --template=plugin --platforms=web .

如果這個命令返回了一個關於需要更新 pubspec.yaml 檔案的提醒,請按照提示的說明進行操作。

Dart 的平台實現

在很多場景中,非 web 平台的實現僅僅使用了上述的平台特定語言。然而,Dart 也是平台特定的語言之一。

純 Dart 平台的實現

如先前描述,通常外掛會使用第二種語言,實現對應平台的功能。然而,在某些場景下,部分平台可能會完全使用 Dart 進行實現(例如使用 FFI)。若需要僅 Dart 的平台實現,你可以將 pubspec.yaml 裡的 pluginClass 替換為 dartPluginClass。下面是 hello_windows 範例替換為僅 Dart 實現的程式碼:

flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        dartPluginClass: HelloPluginWindows

在這樣的模式下,外掛內不包含 Windows 的 C++ 程式碼,它將繼承 hello 外掛的 Dart 平台介面,使用包含靜態 registerWith() 方法的 HelloPluginWindows 類進行實現。該方法會在啟動時呼叫,用於註冊 Dart 實現:

class HelloPluginWindows extends HelloPluginPlatform {
  /// Registers this class as the default instance of [HelloPluginPlatform].
  static void registerWith() {
    HelloPluginPlatform.instance = HelloPluginWindows();
  }

混合平台的實現

平台實現可能同時會使用 Dart 以及某個特定平台的語言。例如,plugin 可能會在不同平台使用不同的 platform channel,這樣 channel 就可以根據不同平台進行客製。

就和之前說的那樣,混合實現將會使用多種註冊方式。這裡有一個使用混合實現的 hello_windows 範例:

flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        dartPluginClass: HelloPluginWindows
        pluginClass: HelloPlugin

Dart 類 HelloPluginWindows 會使用 registerWith() 方法做純 Dart 的實現, HelloPlugin 類則用來做純 C++ 程式碼的實現。

測試你的外掛

我們鼓勵您使用自動化測試來測試您的外掛,以確保程式碼在修改時候功能保持完整。更多資訊,請參見文件 支援新的 Android 的 API 中關於 測試你的外掛 這個小節。

Developing FFI plugin packages

If you want to develop a package that calls into native APIs using Dart’s FFI, you need to develop an FFI plugin package.

Both FFI plugin packages and (non-FFI) plugin packages support bundling native code, but FFI plugin packages do not support method channels and do include method channel registration code. If you want to implement a plugin that uses both method channels and FFI, use a (non-FFI) plugin. You can chose per platform to use an FFI or (non-FFI) plugin.

FFI plugin packages were introduced in Flutter 3.0, if you’re targeting older Flutter versions, you can use a (non-FFI) plugin.

Step 1: Create the package

To create a starter FFI plugin package, use the --template=plugin_ffi flag with flutter create:

$ flutter create --template=plugin_ffi hello

This creates a FFI plugin project in the hello folder with the following specialized content:

lib: The Dart code that defines the API of the plugin, and which calls into the native code using dart:ffi.

src: The native source code, and a CmakeFile.txt file for building that source code into a dynamic library.

platform folders (android, ios, windows, etc.): The build files for building and bundling the native code library with the platform application.

Step 2: Building and bundling native code

The pubspec.yaml specifies FFI plugins as follows:

  plugin:
    platforms:
      some_platform:
        ffiPlugin: true

This configuration invokes the native build for the various target platforms and bundles the binaries in Flutter applications using these FFI plugins.

This can be combined with dartPluginClass, such as when FFI is used for the implementation of one platform in a federated plugin:

  plugin:
    implements: some_other_plugin
    platforms:
      some_platform:
        dartPluginClass: SomeClass
        ffiPlugin: true

A plugin can have both FFI and method channels:

  plugin:
    platforms:
      some_platform:
        pluginClass: SomeName
        ffiPlugin: true

The native build systems that are invoked by FFI (and method channels) plugins are:

  • For Android: Gradle, which invokes the Android NDK for native builds.
    • See the documentation in android/build.gradle.
  • For iOS and MacOS: Xcode, via CocoaPods.
    • See the documentation in ios/hello.podspec.
    • See the documentation in macos/hello.podspec.
  • For Linux and Windows: CMake.
    • See the documentation in linux/CMakeLists.txt.
    • See the documentation in windows/CMakeLists.txt.

Step 3: Binding to native code

To use the native code, bindings in Dart are needed.

To avoid writing these by hand, they are generated from the header file (src/hello.h) by package:ffigen. Regenerate the bindings by running:

$  flutter pub run ffigen --config ffigen.yaml

Step 4: Invoking native code

Very short-running native functions can be directly invoked from any isolate. For an example, see sum in lib/hello.dart.

Longer-running functions should be invoked on a helper isolate to avoid dropping frames in Flutter applications. For an example, see sumAsync in lib/hello.dart.

新增文件

建議將下列文件新增到所有 package 中:

  1. README.md 檔案用來對 package 進行介紹

  2. CHANGELOG.md 檔案用來記錄每個版本的更改

  3. LICENSE 檔案用來闡述 package 的許可條款

  4. API 文件包含所有的公共 API(詳情參見下文)

API 文件

當你提交一個 package 時,會自動產生 API 文件並將其提交到 pub.dev/documentation,範例請參見 device_info 文件。

如果你希望在本地開發環境中產生 API 文件,可以使用以下命令:

  1. 將當前工作目錄切換到 package 所在目錄:
    cd ~/dev/mypackage
    
  2. 告知文件工具 Flutter SDK 所在位置(請自行更改 Flutter SDK 該在的位置)
       export FLUTTER_ROOT=~/dev/flutter  # on macOS or Linux (適用於 macOS 或 Linux 作業系統)
    
       set FLUTTER_ROOT=~/dev/flutter     # on Windows (適用於 Windows 作業系統)
    
  3. 執行 `dartdoc` 工具(已經包含到 Flutter SDK 了):
       $FLUTTER_ROOT/bin/cache/dart-sdk/bin/dartdoc   # on macOS or Linux (適用於 macOS 或 Linux 作業系統)
    
       %FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dartdoc  # on Windows (適用於 Windows 作業系統)
    

關於如何編寫 API 文件的建議,請參閱 高效 Dart 指南

將許可證新增到 LICENSE 檔案中

每個 LICENSE 檔案中的各個許可證應由 80 個短線字元組成的線段進行分割。

如果 LICENSE 檔案中包含多個元件許可證,那麼每個元件許可證必須以其所在 package 的名稱開始,每個 package 名稱單獨一行顯示,並且 package 名稱列表與實際許可證內容由空行隔開。(package 名稱則需與 pub package 相匹配。比如,一個 package 可能包含多個第三方程式碼,並且可能需要為每個 package 新增許可證。)

如下是一些優秀的許可證檔案:

package_1

<some license text>

--------------------------------------------------------------------------------
package_2

<some license text>

這些也是可以的:

package_1

<some license text>

--------------------------------------------------------------------------------
package_1
package_2

<some license text>

這些是一些不太好的範例:

<some license text>

--------------------------------------------------------------------------------
<some license text>

這也是一些不太好的範例:

package_1

<some license text>
--------------------------------------------------------------------------------
<some license text>

提交 package

一旦完成了 package 的實現,你便可以將其提交到 pub.dev 上,以便其他開發者可以輕鬆地使用它。

釋出你的 package 之前,確保檢查了這幾個檔案:pubspec.yamlREADME.mdCHANGELOG.md,確保它們完整且正確,另外,為了提高 package 的可用性,可以考慮加入如下的內容:

  • 程式碼的範例用法

  • 螢幕截圖,GIF 動畫或者影片

  • 程式碼庫的正確指向連結

接下來,執行 dry-run 命令以檢驗是否所有內容都通過了分析:

$ flutter pub publish --dry-run

最後一步是釋出,請注意:釋出是永久性 的,執行以下提交命令:

$ flutter pub publish

設定了中國映象的開發者們請注意:目前所存在的映象都不能(也不應該)進行 package 的上傳。如果你設定了映象,執行上述釋出程式碼可能會造成釋出失敗。網路設定好後,無需取消中文映象,執行下述程式碼可直接上傳:

$ flutter pub publish --server=https://pub.dartlang.org

有關提交的詳細資訊,請查閱關於 Pub 站點的 提交文件

Package 依賴處理

如果你正在開發的 hello 依賴於另外一個 package 所公開的 Dart API,你需要將該 package 新增到檔案 pubspec.yamldependencies 段中。以下程式碼使得外掛 url_launcher 的 Dart API 在 hello 中可用:

dependencies:
  url_launcher: ^5.0.0

現在你可以在 hello 的 Dart 程式碼中使用 import 'package:url_launcher/url_launcher.dart'launch(someUrl)

這與你在 Flutter 應用或其他任何 Dart 專案中引入 package 的方式沒什麼區別。

但碰巧 hello 是一個 原生外掛 package,其特定的平台程式碼如果需要存取 url_launcher 所公開的平台特定 API,那麼還需要為特定平台的建構檔案新增適當的依賴說明,如下所示:

Android

hello/android/build.gradle 檔案中為 url_launcher 外掛設定依賴關係。

android {
    // lines skipped
    dependencies {
        compileOnly rootProject.findProject(":url_launcher")
    }
}

現在你可以在 hello/android/src 目錄下的原始碼檔案中使用 import io.flutter.plugins.urllauncher.UrlLauncherPlugin 並存取檔案 UrlLauncherPlugin

如果希望瞭解更多有關 build.gradle 檔案更多的資訊,請參閱 Gradle 文件 瞭解建構指令碼。

iOS

hello/ios/hello.podspec 檔案中為 url_launcher 外掛設定依賴關係。

Pod::Spec.new do |s|
  # lines skipped
  s.dependency 'url_launcher'

現在你可以在 hello/ios/Classes 目錄下的原始碼檔案中使用 #import "UrlLauncherPlugin.h" 並存取 UrlLauncherPlugin 這個類了。

如果希望瞭解更多有關 .podspec 檔案更多的資訊,請參閱 CocoaPods 文件 瞭解。

Web

與其他的 Dart package 一樣,所有的 Web 依賴都由檔案 pubspec.yaml 來處理。