Flutterを使用する際の一般的なクロスプラットフォームの問題の解決
公開: 2022-03-10Flutterを使用したWeb開発に関してオンラインで多くの混乱を経験しましたが、多くの場合、それは悲しいことに間違った理由によるものです。
具体的には、古いWebベースのモバイル(およびデスクトップ)クロスプラットフォームフレームワークと混同されることがあります。これは基本的に、ラッパーアプリ内で実行されるブラウザー内で実行されるWebページでした。
Webで通常アクセスできるインターフェイスにしかアクセスできなかったため、インターフェイスはとにかく同じであるという意味で、これは本当にクロスプラットフォームでした。
ただし、Flutterはそうではありません。各プラットフォームでネイティブに実行され、AndroidとiOSでJava / KotlinまたはObjective-C / Swiftで記述された場合とほぼ同じように各アプリが実行されることを意味します。 これは、これらの非常に多様なプラットフォーム間の多くの違いに注意を払う必要があることを意味するため、知っておく必要があります。
この記事では、それらの違いのいくつかとそれらを克服する方法を見ていきます。 具体的には、ストレージとUIの違いについて説明します。これらは、クロスプラットフォームにしたいFlutterコードを作成するときに、開発者に混乱をもたらすことが最も多いものです。
例1:ストレージ
私は最近、モバイルアプリと比較してWebアプリにJWTを保存するための異なるアプローチの必要性についてブログに書きました。
これは、プラットフォームのストレージオプションの性質が異なり、それぞれとそのネイティブ開発ツールを知る必要があるためです。
ウェブ
Webアプリを作成する場合、使用できるストレージオプションは次のとおりです。
- ディスクへのファイルのダウンロード/ディスクからのファイルのアップロード。これにはユーザーの操作が必要であるため、ユーザーが読み取るまたは作成することを目的としたファイルにのみ適しています。
- Cookieを使用します。CookieはJSからアクセスできる場合とできない場合があり(
httpOnly
であるかどうかによって異なります)、リクエストとともに特定のドメインに自動的に送信され、応答の一部として保存されます。 - JS
localStorage
とsessionStorage
を使用します。これは、Webサイト上の任意のJSからアクセスできますが、そのWebサイトのページの一部であるJSからのみアクセスできます。
モバイル
モバイルアプリの状況はまったく異なります。 ストレージオプションは次のとおりです。
- そのアプリからアクセス可能なローカルアプリドキュメントまたはキャッシュストレージ。
- ユーザーが作成した/読み取り可能なファイル用の他のローカルストレージパス。
- キー値ストレージ用のiOSとAndroidの
NSUserDefaults
とSharedPreferences
。 - iOSの
Keychain
とAndroidのKeyStore
により、それぞれデータと暗号化キーを安全に保存できます。
それがわからない場合は、実際に使用しているストレージソリューションと、長所と短所を知る必要があるため、実装を混乱させることになります。
クロスプラットフォームソリューション:最初のアプローチ
Flutter shared_preferences
パッケージを使用すると、WebではlocalStorage
、AndroidではSharedPreferences
、iOSではNSUserDefaults
が使用されます。 特にセッショントークンなどの機密情報を保存している場合、これらはアプリにまったく異なる影響を及ぼしますlocalStorage
はクライアントによって読み取られる可能性があるため、XSSに対して脆弱な場合は問題になります。 モバイルアプリはXSSに対して実際には脆弱ではありませんが、 SharedPreferences
とNSUserDefaults
は、安全なストレージではなく、暗号化されていないため、クライアント側で危険にさらされる可能性があるため、安全なストレージ方法ではありません。 これは、iOSの場合とAndroidのドキュメントで、 SharedPreferences
にラッパーを提供してデータを保存する前に暗号化するように設計されているセキュリティライブラリについて説明しているように、ユーザー設定を対象としているためです。
モバイルでの安全なストレージ
モバイルでの唯一の安全なストレージソリューションは、iOSとAndroidでそれぞれKeychain
とKeyStore
ですが、Webには安全なストレージはありません。
ただし、キーKeychain
とキーKeyStore
は本質的に大きく異なります。 Keychain
は一般的な資格情報ストレージソリューションですが、キーKeyStore
は対称キーまたは公開/秘密キーのいずれかの暗号化キーを格納する(および生成できる)ために使用されます。
つまり、たとえば、セッショントークンを保存する必要がある場合、iOSではOSに暗号化部分を管理させ、トークンをKeychain
に送信することができますが、Androidでは、必要なため、少し手動で操作する必要があります。キーを生成するには(ハードコードではなく、それは悪いことです)、それを使用してトークンを暗号化し、暗号化されたトークンをSharedPreferences
に保存し、キーをKeyStore
に保存します。
これにはさまざまなアプローチがありますが、セキュリティのほとんどの場合と同様に、アプリはトークンの暗号化と復号化の両方を行うため、公開鍵暗号化の必要がないため、おそらく対称暗号化を使用するのが最も簡単です。
明らかに、たとえば、すべてを実行するFlutterプラグインがあるため、これらすべてを実行するモバイルプラットフォーム固有のコードを記述する必要はありません。
Web上の安全なストレージの欠如
それが、実際、私がこの記事を書くことを余儀なくされた理由でした。 私はそのパッケージを使用してモバイルアプリにJWTを保存することについて書きましたが、人々はそのWebバージョンを望んでいましたが、私が言ったように、Webには安全なストレージがありません。 存在しません。
それはあなたのJWTがオープンに出なければならないことを意味しますか?
いいえ、まったくありません。 httpOnly
cookieを使用できますね? これらはJSからアクセスできず、サーバーにのみ送信されます。 それに関する問題は、ユーザーの1人が他の誰かのWebサイトでGETリクエストURLをクリックした場合でも、それらは常にサーバーに送信され、そのGETリクエストにはあなたやあなたのユーザーが気に入らない副作用があるということです。 これは実際には他のリクエストタイプでも機能しますが、もっと複雑です。 これはクロスサイトリクエストフォージェリと呼ばれ、それは望ましくありません。 これは、MozillaのMDNドキュメントで言及されているWebセキュリティの脅威の1つであり、より完全な説明を見つけることができます。
予防方法があります。 最も一般的なものは、実際には2つのトークンを持っていることです。1つはhttpOnly
cookieとしてクライアントに到達し、もう1つは応答の一部として取得します。 後者は、サーバーに自動的に送信されたくないため、CookieではなくlocalStorage
に保存する必要があります。
両方を解決する
モバイルアプリとWebアプリの両方がある場合はどうなりますか?
これは、次の2つの方法のいずれかで処理できます。
- 同じバックエンドエンドポイントを使用しますが、Cookie関連のHTTPヘッダーを使用してCookieを手動で取得および送信します。
- Webアプリで使用されるトークンとは異なるトークンを生成する別の非Webバックエンドエンドポイントを作成し、クライアントがモバイル専用トークンを提供できる場合は、通常のJWT認証を許可します。
異なるプラットフォームで異なるコードを実行する
それでは、違いを補うために、さまざまなプラットフォームでさまざまなコードを実行する方法を見てみましょう。
Flutterプラグインの作成
特にストレージの問題を解決するために、プラグインパッケージを使用する方法があります。プラグインは共通のDartインターフェイスを提供し、ネイティブプラットフォーム固有のKotlin / JavaまたはSwift / Objective-Cコードを含むさまざまなプラットフォームでさまざまなコードを実行できます。 パッケージとプラグインの開発はかなり複雑ですが、公式のFlutterドキュメントを含め、Web上の多くの場所(Flutterの本など)で説明されています。
たとえば、モバイルプラットフォームの場合、安全なストレージプラグインがすでに存在します。これはflutter_secure_storage
であり、ここで使用例を見つけることができますが、たとえばWebでは機能しません。
一方、Webでも機能する単純なキー値ストレージの場合、 shared_preferences_web
と呼ばれるクロスプラットフォームのGoogle開発のファーストパーティプラグインパッケージがあります。これには、 NSUserDefaults
、 SharedPreferences
、またはlocalStorage
を使用するshared_preferences
と呼ばれるWeb固有のコンポーネントがあります。プラットフォームによって異なります。
FlutterのTargetPlatform
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ウィジェットを使用するため、さらに注意を払う必要があります。したがって、実際には、切り替えなくても、両方のプラットフォームでアプリを広範囲にテストして、アプリの外観にさらに注意を払う必要があります。 Flutterで可能なように、iOSとマテリアルウィジェットをオンザフライで。
あなたの要求なしに何が変わるか
プラットフォームを切り替えると自動的に変更されるアプリのUIのいくつかの側面があります。 このセクションでは、この点でFlutterとReactNativeの間で何が変わるかについても説明します。
AndroidとiOSの間(フラッター)
FlutterはiOSでマテリアルウィジェット(およびAndroidではクパチーノ(iOSのような)ウィジェット)をレンダリングできますが、AndroidとiOSではまったく同じものを表示しません。マテリアルテーマは特に各プラットフォームの規則に適合します。
たとえば、ナビゲーションアニメーション、トランジション、デフォルトフォントは異なりますが、アプリへの影響はそれほど大きくありません。
美学やUXに関して、選択に影響を与える可能性があるのは、一部の静的要素も変化するという事実です。 具体的には、一部のアイコンが2つのプラットフォーム間で変更され、アプリバーのタイトルがiOSの中央にあり、Androidの左側にあります(戻るボタンまたはドロワーを開くためのボタンがある場合は、使用可能なスペースの左側にあります(ここで説明します)。マテリアルデザインのガイドラインに記載されており、ハンバーガーメニューとも呼ばれます)。Androidでのドロワー付きのマテリアルアプリは次のようになります。
そして、同じ、非常にシンプルなMaterialアプリがiOSでどのように見えるか:
モバイルとWebの間、および画面ノッチ付き(Flutter)
Webでは、フラッターを使用したレスポンシブWeb開発に関するこのSmashingの記事でも述べられているように、少し異なる状況があります。特に、より大きな画面用に最適化し、人々がサイト内を移動する方法を考慮する必要があることに加えて。 —これがその記事の主な焦点です—ウィジェットがブラウザウィンドウの外に配置されることがあるという事実を心配する必要があります。 また、一部の携帯電話では、画面の上部に切り欠きがあるか、何らかの障害物が原因でアプリを正しく表示するためのその他の障害があります。
これらの問題は両方とも、ウィジェットをSafeArea
ウィジェットでラップすることで回避できます。これは、ウィジェットをユーザーの表示を妨げることなく実際に表示できる場所に確実に配置する特定の種類のパディングウィジェットです。ハードウェアまたはソフトウェアの制約です。
ReactNativeで
React Nativeは、両方のプラットフォームでアプリをテストできるようにするために、少なくともiOSシミュレーターとAndroidエミュレーターを実行する必要があることに加えて、各プラットフォームについてより多くの注意とより深い知識を必要とします。同じで、JavaScriptUI要素をプラットフォーム固有のウィジェットに変換します。 つまり、React Nativeアプリは常にiOSのように見えます(Cupertino UI要素と呼ばれることもあります)。Androidアプリは、プラットフォームのウィジェットを使用しているため、常に通常のマテリアルデザインのAndroidアプリのように見えます。
ここでの違いは、Flutterが独自の低レベルレンダリングエンジンを使用してウィジェットをレンダリングすることです。つまり、1つのプラットフォームで両方のアプリバージョンをテストできます。
その問題を回避する
非常に具体的なものを求めているのでない限り、アプリはプラットフォームによって外観が異なるはずです。そうしないと、一部のユーザーが不満を感じることになります。
モバイルアプリを単にウェブに出荷するべきではないのと同じように(前述のSmashingの投稿で書いたように)、たとえば、Androidユーザーにクパチーノウィジェットでいっぱいのアプリを出荷するべきではありません。ほとんどの部分。 一方、別のプラットフォーム向けのウィジェットを備えたアプリを実際に実行する機会があると、アプリをテストして、必ずしも2つのデバイスを使用しなくても、両方のバージョンのユーザーに表示できます。
反対側:正しい理由で間違ったウィジェットを使用する
ただし、これは、iOSユーザーのエクスペリエンスを犠牲にすることなくLinuxまたはWindowsワークステーションでFlutter開発のほとんどを実行でき、他のプラットフォーム用のアプリをビルドするだけで、徹底的なテストについて心配する必要がないことも意味します。
次のステップ
クロスプラットフォームフレームワークは素晴らしいですが、各プラットフォームがどのように機能するか、アプリがユーザーにとって快適に使用できるようにする方法を理解する責任を開発者に移します。 考慮すべき他の小さなことは、たとえば、異なるプラットフォームに異なる規則がある場合、本質的に同じものである可能性があるものについて異なる説明を使用することです。
異なる言語を使用して2つ(またはそれ以上)のアプリを別々に作成する必要がないのは素晴らしいことですが、本質的には複数のアプリを作成していること、および作成している各アプリについて考える必要があることを覚えておく必要があります。 。
その他のリソース
- Flutter GalleryのWebサイトとAndroidアプリ。さまざまなプラットフォームに典型的なFlutterウィジェットの使用と、プラットフォームにとらわれない機能を紹介しています。
- TargetPlatformのFlutterAPIドキュメント
- パッケージとプラグインの作成に関するフラッタードキュメント
- プラットフォームの適応に関するフラッタードキュメント
- Cookieに関するMDNドキュメント