解決使用 Flutter 時常見的跨平台問題

已發表: 2022-03-10
快速總結↬使用跨平台框架時,人們可能會忘記他們希望代碼運行的每個平台的細微差別。 本文旨在解決這個問題。

我在網上看到很多關於使用 Flutter 進行 Web 開發的困惑,而且很遺憾,這通常是出於錯誤的原因。

具體來說,人們有時會將其與舊的基於 Web 的移動(和桌面)跨平台框架相混淆,後者基本上只是在包裝應用程序中運行的瀏覽器中運行的網頁。

從某種意義上說,這確實是跨平台的,因為您只能訪問通常在 Web 上可訪問的界面,因為您只能訪問相同的界面。

然而,Flutter 並非如此:它在每個平台上原生運行,這意味著每個應用程序的運行就像在 Android 和 iOS 上用 Java/Kotlin 或 Objective-C/Swift 編寫時一樣。 您需要知道這一點,因為這意味著您需要注意這些非常多樣化的平台之間的許多差異。

在本文中,我們將看到其中的一些差異以及如何克服它們。 更具體地說,我們將討論存儲和 UI 差異,這是開發人員在編寫他們想要跨平台的 Flutter 代碼時最常引起混淆的差異。

跳躍後更多! 繼續往下看↓

示例 1:存儲

我最近在我的博客上寫道,與移動應用程序相比,在 Web 應用程序中存儲 JWT 需要一種不同的方法。

這是因為平台存儲選項的性質不同,並且需要了解每個平台及其原生開發工具。

網絡

當您編寫 Web 應用程序時,您擁有的存儲選項是:

  1. 向/從磁盤下載/上傳文件,這需要用戶交互,因此僅適用於打算由用戶讀取或創建的文件;
  2. 使用 cookie,可能會或可能無法從 JS 訪問(取決於它們是否為httpOnly ),並與請求一起自動發送到給定域並在它們作為響應的一部分出現時保存;
  3. 使用 JS localStoragesessionStorage ,網站上的任何 JS 都可以訪問,但只能從作為該網站頁面一部分的 JS 訪問。

移動的

移動應用程序的情況完全不同。 存儲選項如下:

  1. 該應用程序可訪問的本地應用程序文檔或緩存存儲;
  2. 用戶創建/可讀文件的其他本地存儲路徑;
  3. NSUserDefaultsSharedPreferences分別在 iOS 和 Android 上用於 key-value 存儲;
  4. iOS 上的Keychain和 Android 上的KeyStore ,分別用於安全存儲任何數據和加密密鑰。

如果您不知道這一點,您的實現就會變得一團糟,因為您需要知道您實際使用的存儲解決方案以及優缺點是什麼。

跨平台解決方案:初步方法

使用 Flutter shared_preferences包在 Web 上使用localStorage ,在 Android 上使用SharedPreferences在 iOS 上使用NSUserDefaults 。 這些對您的應用程序有完全不同的影響,特別是如果您要存儲會話令牌之類的敏感信息:客戶端可以讀取localStorage ,因此如果您容易受到 XSS 的攻擊,這是一個問題。 儘管移動應用程序並不真正容易受到 XSS 的攻擊,但SharedPreferencesNSUserDefaults不是安全存儲方法,因為它們可能在客戶端受到破壞,因為它們不是安全存儲且未加密。 這是因為它們是針對用戶偏好的,正如這裡在 iOS 和 Android 文檔中提到的那樣,在談論安全庫時,該庫旨在為SharedPreferences提供包裝器,專門用於在存儲數據之前對數據進行加密。

移動設備上的安全存儲

移動設備上唯一的安全存儲解決方案分別是 iOS 和 Android 上的KeychainKeyStore ,而Web 上沒有安全存儲

但是, KeychainKeyStore在本質上是非常不同的: Keychain是一種通用的憑證存儲解決方案,而KeyStore用於存儲(並且可以生成)加密密鑰,可以是對稱密鑰,也可以是公鑰/私鑰。

這意味著,例如,如果您需要存儲會話令牌,則在 iOS 上,您可以讓操作系統管理加密部分並將您的令牌發送到Keychain ,而在 Android 上,這更像是手動體驗,因為您需要生成(不是硬編碼,這很糟糕)密鑰,使用它來加密令牌,將加密的令牌存儲在SharedPreferences並將密鑰存儲在KeyStore

有不同的方法,就像安全方面的大多數事情一樣,但最簡單的可能是使用對稱加密,因為不需要公鑰加密,因為您的應用程序同時加密和解密令牌。

顯然,您不需要編寫所有這些工作的特定於移動平台的代碼,例如,有一個 Flutter 插件可以完成所有這些工作。

網絡上缺乏安全存儲

實際上,這就是迫使我寫這篇文章的原因。 我寫過關於使用該軟件包將 JWT 存儲在移動應用程序上的文章,人們想要它的 Web 版本,但正如我所說, Web 上沒有安全存儲。 它不存在。

這是否意味著您的 JWT 必須公開?

一點都不。 您可以使用httpOnly cookie,不是嗎? 這些是 JS 無法訪問的,只會發送到您的服務器。 這樣做的問題是它們總是被發送到您的服務器,即使您的一個用戶單擊其他人網站上的 GET 請求 URL,並且該 GET 請求具有您或您的用戶不喜歡的副作用。 這實際上也適用於其他請求類型,只是更複雜。 它被稱為跨站點請求偽造,您不希望這樣。 它是 Mozilla 的 MDN 文檔中提到的網絡安全威脅之一,您可以在其中找到更完整的解釋。

有預防方法。 實際上,最常見的是有兩個令牌:一個作為httpOnly cookie 到達客戶端,另一個作為響應的一部分。 後者必須存儲在localStorage而不是 cookie 中,因為我們不希望它自動發送到服務器。

解決兩者

如果您同時擁有移動應用程序和 Web 應用程序怎麼辦?

這可以通過以下兩種方式之一處理:

  1. 使用相同的後端端點,但使用與 cookie 相關的 HTTP 標頭手動獲取和發送 cookie;
  2. 創建一個單獨的非 Web 後端端點,該端點生成與 Web 應用程序使用的任一令牌不同的令牌,然後如果客戶端能夠提供僅限移動設備的令牌,則允許常規 JWT 授權。

在不同的平台上運行不同的代碼

現在,讓我們看看我們如何在不同的平台上運行不同的代碼,以便能夠彌補差異。

創建一個 Flutter 插件

尤其是解決存儲問題,一種方法是使用插件包:插件提供通用的 Dart 接口,可以在不同的平台上運行不同的代碼,包括原生平台特定的 Kotlin/Java 或 Swift/Objective-C 代碼. 開發包和插件相當複雜,但在 Web 和其他地方(例如 Flutter 書籍)的許多地方都有解釋,包括 Flutter 官方文檔。

例如,對於移動平台,已經有一個安全存儲插件,即flutter_secure_storage ,您可以在此處找到一個使用示例,但例如,它在 Web 上不起作用。

另一方面,對於也適用於 Web 的簡單鍵值存儲,有一個跨平台的 Google 開發的第一方插件包,稱為shared_preferences ,它有一個稱為shared_preferences_web的 Web 特定組件,它使用NSUserDefaultsSharedPreferenceslocalStorage取決於平台。

顫振上的目標平台

導入package:flutter/foundation.dart後,您可以將Theme.of(context).platform與值進行比較:

  • TargetPlatform.android
  • TargetPlatform.iOS
  • TargetPlatform.linux
  • TargetPlatform.windows
  • TargetPlatform.macOS
  • TargetPlatform.fuchsia

並編寫您的函數,以便對於您要支持的每個平台,它們執行適當的操作。 這對於下一個平台差異示例特別有用,即小部件在不同平台上的顯示方式差異。

特別是對於那個用例,還有一個相當流行的flutter_platform_widgets插件,它簡化了平台感知小部件的開發。

示例 2:相同小部件的顯示方式不同

你不能僅僅編寫跨平台代碼就假裝瀏覽器、手機、電腦和智能手錶是同一回事——除非你希望你的 Android 和 iOS 應用程序是一個 WebView,而你的桌面應用程序是用 Electron 構建的. 有很多理由不這樣做,本文的重點不是說服您使用 Flutter 之類的框架,而不是讓您的應用程序保持原生,並具有隨之而來的所有性能和用戶體驗優勢,同時允許您編寫大多數時間在所有平台上都相同的代碼。

但是,這需要細心和關注,並且至少對您想要支持的平台、它們的實際原生 API 以及所有這些有基本的了解。 React Native 用戶需要更加註意這一點,因為該框架使用內置的 OS 小部件,因此您實際上需要通過在兩個平台上進行廣泛測試來更加註意應用程序的外觀,而不能在兩者之間切換iOS 和 Material 小部件就像 Flutter 一樣。

沒有您的要求會發生什麼變化

當您切換平台時,您的應用程序 UI 的某些方面會自動更改。 本節還提到了 Flutter 和 React Native 在這方面的變化。

Android 和 iOS 之間 (Flutter)

Flutter 能夠在 iOS 上渲染 Material 小部件(以及在 Android 上渲染 Cupertino(類 iOS)小部件),但它不做的是在 Android 和 iOS 上顯示完全相同的東西:Material 主題特別適應每個平台的約定.

例如,導航動畫和過渡以及默認字體是不同的,但它們不會對您的應用產生太大影響。

在美學或用戶體驗方面可能會影響您的一些選擇的是,一些靜態元素也會發生變化。 具體來說,一些圖標在兩個平台之間會發生變化,應用欄標題在 iOS 上位於中間,在 Android 上位於左側(在可用空間的左側,以防有後退按鈕或打開抽屜的按鈕(此處解釋)在 Material Design 指南中,也稱為漢堡菜單)。這是帶有 Drawer 的 Material 應用在 Android 上的外觀:

Android 應用程序的圖像,顯示了應用程序欄標題在 Flutter Android Material 應用程序上的顯示位置
在 Android 上運行的 Material 應用:AppBar 標題位於可用空間的左側。 (大預覽)

同樣的,非常簡單的 Material 應用在 iOS 上的樣子:

iOS 應用程序的圖像,顯示了應用程序欄標題在 Flutter iOS Material 應用程序上的顯示位置
在 iOS 上運行的 Material 應用:AppBar 標題在中間。 (大預覽)

在移動設備和 Web 之間使用屏幕缺口 (Flutter)

在 Web 上有一些不同的情況,正如這篇關於使用 Flutter 進行響應式 Web 開發的 Smashing 文章中所提到的:特別是,除了必須針對更大的屏幕進行優化並考慮人們期望瀏覽您的站點的方式之外——這是那篇文章的主要焦點——你不得不擔心有時小部件被放置在瀏覽器窗口之外的事實。 此外,某些手機的屏幕頂部有凹口,或者由於某種障礙而妨礙正確查看您的應用程序。

通過將小部件包裝在SafeArea小部件中可以避免這兩個問題,這是一種特殊類型的填充小部件,可確保您的小部件落入可以實際顯示的位置,而不會妨礙用戶查看它們的能力,無論是硬件約束還是軟件約束。

在反應原生

React Native 需要更多的關注和對每個平台的更深入的了解,此外還要求您至少運行 iOS 模擬器和 Android 模擬器以便能夠在兩個平台上測試您的應用程序:它不是同樣,它將其 JavaScript UI 元素轉換為特定於平台的小部件。 換句話說,您的 React Native 應用程序將始終看起來像 iOS — 帶有有時被稱為 Cupertino UI 元素 — 而您的 Android 應用程序將始終看起來像常規 Material Design Android 應用程序,因為它使用平台的小部件。

這裡的不同之處在於 Flutter 使用自己的低級渲染引擎渲染其小部件,這意味著您可以在一個平台上測試兩個應用程序版本。

解決這個問題

除非你想要一些非常具體的東西,否則你的應用程序應該在不同的平台上看起來不同,否則你的一些用戶會不高興。

就像您不應該簡單地將移動應用程序發佈到網絡上一樣(正如我在前面提到的 Smashing 帖子中所寫的那樣),您也不應該將一個充滿 Cupertino 小部件的應用程序發布給 Android 用戶,因為這會讓您感到困惑。大部分。 另一方面,有機會實際運行具有適用於另一個平台的小部件的應用程序,您可以測試該應用程序並將其展示給兩個版本的人,而不必為此使用兩台設備。

另一面:出於正確的原因使用錯誤的小部件

但這也意味著您可以在 Linux 或 Windows 工作站上進行大部分 Flutter 開發,而不會犧牲 iOS 用戶的體驗,然後只需為其他平台構建應用程序,而不必擔心對其進行徹底測試。

下一步

跨平台框架很棒,但它們將責任轉移給開發人員,以了解每個平台的工作原理以及如何確保您的應用程序適應並為您的用戶帶來愉快的使用。 其他需要考慮的小事情可能是,例如,如果不同平台上有不同的約定,則對本質上可能是同一事物的事物使用不同的描述。

不必使用不同的語言分別構建兩個(或更多)應用程序很好,但您仍然需要記住,本質上,您正在構建多個應用程序,這需要考慮您正在構建的每個應用程序.

更多資源

  • Flutter Gallery 網站和 Android 應用程序,展示了不同平台典型的 Flutter 小部件的使用及其平台無關性
  • TargetPlatform 上的 Flutter API 文檔
  • 關於創建包和插件的 Flutter 文檔
  • Flutter 平台適配文檔
  • 關於 cookie 的 MDN 文檔