iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0

概覽

Flutter 很幸運有一個不錯的開發者社群,通過「插件 Plugin」的方式分享彼此程式碼。也就是這種開源的形式讓 Flutter 這類的框架得以發展。這也表示一般來說你可以不用重新造輪子,可以聚焦在你的應用程式功能。

這裡我們將探索插件以及了解該如何使用。各式各樣的插件從使用者介面組件的函式庫到一些底層的工具,音樂管理的功能等等你可以搜尋自己所需要的功能。雖然每一個插件各自有自己的設定方式,但都需要遵循通用的規範和版本管理。我們將逐一了解。

我們將看看一些常見的問題,以及如何使用插件。通過插件我們可以省去自己開發許多功能,並聚焦在自己的應用。

接著,我們將探索:

  • 何謂插件?
  • 如何找到插件
  • 如何安裝插件
  • 插件在不同平台的運作
  • 常見議題

何謂插件?

大部分的程式框架和軟體工具都有插件的概念。可能使用不同名稱例如第三方函式庫,擴充套件 extension,附加元件 add-ons 等,但指的都是同一件事;一個獨立,模組化的程式可以被「添加」「外掛」「插入」到已存在的應用程式,目的是增加額外其他的功能。

在 Flutter 中 package 套件一詞,指的是由一系列 Dart 檔案可包含其他資源檔如圖片,字體等組成。而 plugin 插件是一種特殊類型的套件,使用標準化的方式為你的應用程式增加功能。

也就是插件是一種特殊套件,更具體的說插件是 Flutter 和平台原生程式碼之間的橋樑。讓我們可以存取原生平台的功能,而「套件」單純只是 Dart 和 Flutter 程式碼的集合,在 Flutter 專案中提供可重用的功能,和任何平台沒有相依關係。

插件的形式當然有優點和缺點。因此,讓我們從了解插件對 Flutter 框架的好處,以及應該考慮的缺點開始。

package 和 plugin 的差異

插件(plugin)是一種套件(package) 完整的名稱為 plugin package,一般簡稱 plugin。

套件:最低限度的 Dart 套件是一個目錄包含 pubspec.yaml 檔案。一個套件可以包含 Dart 函式庫、應用程式、資源檔案等等。在 https://pub.dev 網站上可以找到由社群開發者提供的套件。
插件:為特殊的套件,通常是為了提供平台的功能。例如針對 Android 提供相機功能的插件,其會使用 Kotlin 或 Java 。

優點

重複使用程式

身為開發者,我們都希望時間最好能夠聚焦在我們的功能開發。花時間重新開發一些已經被其他開發者多次編寫的程式碼並不能提高效率,特別是當一些通用的功能並不是你的應用程式重點時。例如使用第三方服務的情況,常見如 Google Map、Firebase 等都已經有開發者在 Flutter 中使用,此時使用這些插件可以節省不少時間。

而且它們可能都考慮到了其他更多情況,甚至比自己開發的問題要少。畢竟這不是你核心的關注點或專業領域。

另外,若是自己處理,當服務有更新或有 Bug 的時候會花費你更多時間。

套件就是通過解決特定問題如處理日曆相關議題來降低開發者可能要花大量時間關注非核心議題。理論上所有應用程式都可以使用某一個套件,然後當發現問題時,修正套件並發佈新版本之後。所有使用套件的應用程式只需要更新相依函式庫就可以得到修正。

套件其中的一個好處就是因為很多人使用,進而可以一起發現問題,解決問題,讓套件變的越來越穩定且豐富功能。

在某些情況,你可能覺得自己撰寫比較容易,但卻很難達成在各種不同的裝置測試程式碼。而套件卻可以免費的在各種裝置,不同螢幕比例等狀況下測試,套件的開發者可以進而修正以適應各種情形。當然如果在你的應用程式中有使用者發現問題。我們也可以回報給作者或協作社群的開發者,他們通常很願意修正問題。

領域的專業知識

針對一些功能,你可能需要整合裝置的作業系統。可能會需要例如 Java、Swift、JavaScript 或其他語言的知識,除非你精通那些語言以及作業系統的 API,不然這會是蠻大的挑戰。幸虧,其他 Flutter 開發者有人有相關的專業知識,並且建立的插件讓我們可以輕易的使用平台提供的功能。

這也表示,我們不需要額外學習底層的技術,你可以專注在 Dart 和 Flutter 即可,至少在剛入門的時候可以;隨著經驗豐富可能你會想要更加深入那些領域,學習各平台底層的知識。

類似的情況,也可能套件是針對特定領域例如安全性。Flutter 社群擁有許多不同專業領域的專家,因此使用他們的套件比起自己花時間研究可能會是不錯的選擇。

到此,聽起來好像套件只有優點,但其實套件也有一些缺點下面我們就來看看有哪些?

缺點

跟任何軟體決策一樣,尤其是在套件這樣龐大的規模上,使用時必須考慮其缺點。

版本管理

跟很多套件系統一樣,在 Flutter 的套件也使用語意化版本來管理。這表示我們可以輕易的管理安裝套件的版本,確保我們盡可能使用最新的版本。

然而,任何版本系統都有潛在不相容的問題,特別是當你相依大量套件的時候。這確實會造成問題,並且在一些極端的情況下甚至會妨礙你建置應用程式。

例如:你使用了套件 A 和 B 它們都在內部相依了套件 C ,這個 C 就可能會發生所謂的傳遞性依賴問題。

假設我們使用了套件 A 和 B 都使用了套件 C 1.0 版本。現在,套件 C 發佈了 2.0,且這個 2.0 和 1.0 不相容。

此時,套件 A 的作者決定升級,而套件 B 的還沒升級。那麼我們就會遭遇:

  • 如果更新套件 A 那麼會要求 C 須 2.0,但套件 B 還在使用 1.0 會導致版本衝突
  • 若不升級套件 A 那麼就無法使用最新的錯誤修復

在套件 B 升級之前,我們可能都不能升級套件 A 或者要替換掉 B。這就是傳遞性依賴可能帶來的問題。

語意化版本

軟體的版本在一般軟體專案都是必須的。它可以帶來很多好處。而語意化版本 Semantic Version 標準化了版本控制的完成方式。

即使用例如 1.2.3 的格式表示版本。 這個格式表達 major.minor.patch 的結構,分別是主版號變更表示重大變更,通常你的程式碼也需要調整。次版號通常是效能改進,主要錯誤修正,或內部相依變更,次版號更新通常是向下相容的。最後當有一些低風險 Bug 修正或簡單的調整會變更最後的版號。

難以除錯

一些複雜的套件在問題發生時可能不容易解決問題。一般來說,套件作者會很願意協助處理你發現的問題。但有些錯誤很難複現,這也會造成困擾。

在很多方面,問題不是因為套件有 Bug ,而是因為那不是你寫的,因此很難除錯。反過來說,如果自己開發套件的功能,可能也會遇到問題,你需要維護更多程式碼。可能減少專注在核心功能的時間。

總體來說,我們還是會選擇使用套件。如果發現的問題對我們影響很大,我們可以選擇協助修正,因為大部分套件都採取開源的形式。多數情況下,你是可以在本地機器上修改套件並測試修復。

重大不相容變更

有時候,套件會有重大變更,可能因為作者希望從底層基礎做些改變,作者想改變套件的使用方式,參數或其他理由。

套件作者將會建立一個新版本,主版號變更,接著警告開發者們發生這些改變。你可以不用一定要接受新版本,但通常開發者不太會花費太多心力維護舊版本,因此維持更新是比較好的選擇。

有時候這會大量影響你的程式碼,特別是你非常依賴套件時。對於比較新的套件或和第三方服務整合的套件來說,這很常發生,例如一個金流服務流程加入 3D 驗證時。此時我們的應用程式也會需要進行大量修正。類似的情況還有套件作者為了長期的維護和一些考量決定要重構。在 AppRouter 版本 6 時就真實發生過;路由的識別從程式碼產生轉移到註釋。

廢棄的插件

還有時候,套件作者可能會放棄套件,不再提供更新、回覆、修正錯誤。在這種情況,我們會被迫要嘛自己重寫,要嘛尋找新的套件。通常這都會造成很大的影響。一般來說這通常發生在出現了可替代的套件,且這個套件變的更加主流。例如:套件一開始是熱心開發者為了和第三方服務整合開發的,然後官方開始支援開發了新套件。到此,我們已經對套件有了一定的認識,也明白了使用套件的優缺點。

如何尋找插件?

要搜尋 Flutter 套件其實非常容易,我們可以到 https://pub.dev 尋找。這是所有套件註冊的地方。這個網站不只能搜尋套件,也會包含下面實用的資訊:

  • Flutter Favorites 推薦高質量的套件
  • Most popular packages 過去 60 天下載量最高的套件
  • Package of the Week 一系列動畫影片介紹套件。

不意外的是我們會找到很多套件它們的功能是類似的,在過去要選擇出最主流,維護最好的套件是一件困難的事。我們不會想選擇一個不再維護的套件,又或者很少人使用;表示這套件可能沒有經過各種環境的測試;發生問題的時候不容易找到處理問題的資訊。

現在,有了這個網站你可以輕鬆的比較各種指標其中比較關鍵的資訊為:

  • 更新日期:確定套件是否持續維護
  • 支援的平台
  • 標籤,例如 Null safety

其他可以協助選擇的資訊如

  • Likes
  • Pub points 分析套件評分滿分 160,會評估維護,文件,平台支援度,相依套件是否更新等
  • Popularity 該指標呈現多少應用程式使用

通過上面的資訊我們可以有效的評估是否採用某個套件。

其他資訊還有例如:

  • Readme 專案說明
  • Changelog 變更紀錄
  • Example 範例
  • Installing 安裝資訊
  • Repository 程式的檔案庫
  • View 重要連結

如何在專案中加入插件

安裝套件通常很簡單,但偶爾會涉及到平台設定,這時我們會需要參考 Readme 或安裝的說明。

一般有兩種方式安裝,這兩種方式的結果是一樣的。第一種是使用 flutter pub add 指令

$ flutter pub add auto_size_text

另一種則是搭配 pubspec.yaml 檔案。

pubspec.yaml

pubspec.yaml 檔案包含了相依套件的設定,其中 dependencies 包含了這個專案相依的套件如下:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2

比如上面我們安裝的 auto_size_text 我們可以在這裡加入設定

auto_size_text: ^3.0.0

在這個設定中我們還可以指定版本,也相對容易將資訊分享給其他開發者。但是,單純加入設定本身不會安裝套件,還需要搭配指令或者 VS Code 自動安裝的功能。

如果你沒有使用支援安裝的編輯器,那麼需要自行執行指令進行安裝:

$ flutter pub get

flutter pub 指令

flutter 指令中有一個 pub 指令,它支援一些功能協助我們管理套件。讓我們先來看看上面那個最常見的指令

$ flutter pub get

這個指令會讀取 pubspec.yaml 檔案,並且從 pub.dev 檢索相關套件以及下載到本地專案中。例如上面我們加入 auto_size_text 插件之後執行該指令並會看到對應的操作。

$ flutter pub outdated

這個指令是用來檢查相依的套件是否有新版本。執行之後會得到類似下面的執行

Showing outdated packages.
[*] indicates versions that are not the latest available.

Package Name     Current  Upgradable  Resolvable  Latest

direct dependencies:
cupertino_icons  *1.0.6   1.0.8       1.0.8       1.0.8

會告訴我們哪些套件已經有新版本以及我們當前的版本。

$ flutter pub upgrade

提到語意化版本我們忘了提到關於一些前綴符號,例如 ^ 表示可以更新 patch 就是第三數字的版本。然後我們就可以搭配 flutter pub upgrade 來更新,這個指令和 flutter pub get 類似都會檢索 pubspec.yaml 然後根據我們設定的版本進行更新。

更多的指令可以參考

使用插件

到此我們已經安裝了插件,接著要使用其功能我們需要先匯入。

import 'package:flutter/material.dart';

跟其他程式語言一樣,import 可以引用其他類別、套件和插件中的程式碼。這裡我們匯入了 material.dart 後續我們就可以使用其中的類別和函式。

在我們的編輯工具 lib 目錄下建立檔案 description_widget.dart 加入

class DescriptionWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

此時,我們會注意到錯誤,這是因為我們沒有匯入 import 相關套件。在這個檔案中,StatelessWidget 類別不存在,因此編譯器無法處理。

我們會需要匯入 Flutter 框架中一些標準類別:

import 'package:flutter/material.dart';

接著加入建構子

import 'package:flutter/material.dart';

class DescriptionWidget extends StatelessWidget {
  final String description;

  const DescriptionWidget({ super.key, required this.description});

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

現在讓我們來使用 AutoSizeText 組件:

import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';

class DescriptionWidget extends StatelessWidget {
  final String description;

  const DescriptionWidget({ super.key, required this.description});

  @override
  Widget build(BuildContext context) {
    return AutoSizeText(
      description,
      style: const TextStyle(color: Colors.red),
    );
  }
}

插件如何在 iOS 和 Android 運作?

為了使用平台作業系統提供的功能,許多插件會和平台底層互動。這樣的相依關係因為使用了原生程式碼進而影響了專案的建置和架構。下面讓我們來看看是如何運作。

MethodChannel

Flutter 通過平台頻道來達成客戶端(Flutter)和原生應用之間的溝通。MethodChannel 類別的功用就是傳送訊息到平台。在平台端,Android 使用 MethodChannel(API),而 iOS 使用 FlutterMethodChannel(API)來接收「呼叫方法」且回傳結果。這種關係的結構如以下圖表所示。

平台頻道技術讓我們可以解耦 UI 和特定平台的原生程式。平台應用只需要監聽平台頻道,接收訊息請求。然後使用平台的 API 來處理請求並將結果回傳給客戶端,也就是回到 Flutter 的部分。

通過這種方式,Flutter 應用程式的部分可以不用依賴特定平台,因此實現跨平台。

對於插件基本的理解我們後續在開發時可以更好的運用和除錯。

下面讓我們通過使用 device_info_plus 來了解一下專案會如何組織使用原生程式碼。這個簡單的插件可以檢索裝置的資訊如模組和裝置名稱。

$ flutter pub add device_info_plus

指令安裝之後我們可以看到 device_info_plus 加入 pubspec.yaml 檔案中,但此時我們還沒有原生程式的部分。如果我們執行專案或建置專案,Flutter 會自動讀取相依的原生程式,在 iOS 中使用 CocoaPods 管理,Android 則是 Gradle.

對於 iOS 函式庫,Flutter 使用 CocoaPods 相依管理工具。Flutter 插件需要的原生程式會通過 CocoaPods 下載安裝並管理版本。當我們執行專案後會發現在 iOS 目錄下有個 Podfile,然後 CocoaPods 會自動在 ios 目錄下執行 pod install 後續原生程式的部分就會下載安裝。

Flutter 3.24 開始支援 Swift Package Manager,介接 Swift 套件生態系,且由於 Swift Package Manager 與 Xcode 綑綁在一起,開發者使用 Flutter 開發Apple 平臺應用,便不再需要安裝 Ruby 和 CocoaPods。

有些插件還會需要我們要求權限。例如需要使用相機功能,或者通訊錄。此時我們需要設定 ios/Runner/Info.plist 檔案。後續裝置會向使用者要求授權。

而 Android 則使用 Gradle 自動化工具。在 android 目錄下有時候我們會需要設定 android/build.gradleandroid/app/build.gradle 。這些檔案複雜管理建置流程和相依套件。偶爾會需要我們調整版本或建置流程。

此外,和 iOS 一樣,一些插件會需要要求授權,這個時候我們需要編輯 android/app/src/main/AndroidManifest.xml 檔案。

到此我們已經具備了基礎的知識。

常見問題

有時候,Flutter 執行或建置失敗,很多時候是插件的關係。這裡我們來看一些常見的問題以及如何解決。

不相容變更

當插件主版號變了的時候,可能意味著有不相容的變更,而且我們需要調整程式碼。

一般來說有兩個原因:

  • 插件的使用方式不同了。例如建構子的參數不同,或者方法變了。這種時候通常我們需要調整程式碼。一般這種情況在建置錯誤的訊息中會提高一些提示。
  • 專案設定須配合變更。這種情況通常比較不明顯需要閱讀官方說明進行調整

在 pub.dev 有個 Changelog 區塊專門提供相關資訊。

在這裡,插件開發者通常會列出每次版本發生的更新,包括指出不相容的變更,以便應用開發者能夠輕鬆看到他們需要做出的更改。例如 device_info_plus ,主要版本更新有多次不相容的變更。

插件開發者偶爾也會犯錯,在沒有主要版本變更的情況下釋出不相容的變更。插件開發者通常是自願投入時間進行插件開發,因此他們可能會因時間不足或經驗不足而犯錯。通常,當這種情況被告知後,他們會在下一個次要版本中撤回破壞性變更,然後再釋出包含破壞性變更的主要版本。因此,如果你遇到任何問題,值得查看 pub.dev 上的 README 和變更日誌,以確認是否有任何偷偷的破壞性變更進入更新中。

插件無法運作

有時候你可能會遇到插件不如預期般那樣運作,這個時候可以到 pub.dev 或 Github 上查看是否有相關 Issue。你可以在討論中找到相關進度或解決方法。

因為大部分插件都是社群開發的,通常其他開發者會嘗試解決問題並提交 PR 給作者。隨著我們對於 Flutter 深入了解嘗試一起解決問題不僅能為社群帶來貢獻,也能提升自己的能力。

另外,你可能也會遇到一些 Issue 有人提交了 PR 但沒有被合併。這常常發生在一些比較不流行的插件,因為協作者比較少,核心作者可能沒時間或已經轉移到其他插件。

然而即便 PR 沒有合併,我們依然可以在我們的專案使用,此時我們可以在 pubspec.yaml 使用 dependency_overrides 並加入 git 連結來替換。

dependency_overrides:
  device_calendar:
    git:
      url:
      https://github.com/thomassth/device_calendar_null.git

不一致的相依

任何相依套件管理系統,當一個套件依賴特定版本的套件,而另一個套件卻相依另一個版本就會造成不一致的依賴。

通常解決方法是確保套件都更新到最新,如果這樣無法解決則需要降版。這裡就如同上面補充的例子套件 A 相依套件 C,套件 B 也相依套件 C,當兩者對 C 依賴的版本不同造成問題。

在 iOS 當我們編譯的時候,CocoaPods 有時候會發生例外,這時我們可以通過手動安裝來查看錯誤訊息。在 ios 目錄下執行 pod install 會產生錯誤訊息。通常我們會嘗試移除 ios/Pods 目錄和 Podfile.lock 然後執行 pod install 重新安裝。除此之外,有時候我們會需要更新 CocoaPods 檔案庫。

$ pod repo update

若這樣還無法解決問題,通常我們會查看相關插件的 Github Issues。

MissingPluginException

除了上面介紹的常見問題之外,MissingPluginException 也是常見的例外。通常使用 flutter clean 來清除一些相關目錄可以解決這個問題。

可以使用如下指令:

$ flutter clean
$ flutter pub get

如此便可以清除相關目錄,重新下載插件。

其他還有很多問題,通常如果其他人應該已經遇到了或正在處理,一般我們可以查詢 Github Issues 或 StackOverflow 找到答案。

  • No MediaQuery widget found 注意須搭配 MaterialApp

  • RenderFlex Unbounded Error 常見因為 ColumnExpanded 組織結構錯誤例如

    Screen
      → Column
        → Column
          → Expanded → ERROR
    
  • setState() called after dispose() 注意組件是否已經釋放。

  • A RenderFlex overflowed by pixels on the bottom 通常是由於 Column 或 Row 中的子組件超出了其父組件的邊界

總之很多錯誤,你只需要稍微搜尋一下便會發現已經有人發問過了。到此,我們已經了解了插件的優點和缺點、需要注意的地方、如何搜尋插件和使用等等相關的知識。


上一篇
Day 18 狀態管理
下一篇
Day 20 探索主流第三方插件
系列文
Flutter 開發實戰 - 30 天逃離新手村26
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言