チュートリアル: バックエンド サービス経由で Azure Notification Hubs を使用して Flutter アプリにプッシュ通知を送信する
サンプル サンプル をダウンロードする
- Xamarin.Forms の
- Flutter
- React ネイティブ の
このチュートリアルでは、
ASP.NET Core Web API バックエンドは、最新かつ最適な インストール アプローチを使用して、クライアントの デバイス登録 を処理するために使用されます。 また、サービスはクロスプラットフォーム方式でプッシュ通知を送信します。
これらの操作は、バックエンド操作の
このチュートリアルでは、次の手順を実行します。
前提 条件
これに従うには、次のものが必要です。
- Azure サブスクリプション リソースを作成および管理できます。
- Flutter ツールキット (その前提条件と共に)。
Flutter プラグインと Dart プラグインがインストールされている Visual Studio Code します。 - CocoaPods ライブラリの依存関係を管理するためにインストールされます。
Android (物理デバイスまたはエミュレーター デバイス) または iOS (物理デバイスのみ)でアプリを実行する機能。
Android の場合は、次が必要です。
- 開発者が物理デバイスのロックを解除するか、エミュレーター (Google Play Services がインストールされた状態で API 26 以降を実行)。
iOS の場合は、次が必要です。
- アクティブな Apple Developer Account。
- (iOS 13.0 以降を実行している)
開発者アカウントに登録 。物理 iOS デバイス .p12 開発証明書 キーチェーンにインストール 、物理デバイスでアプリを実行できます。
手記
iOS シミュレーターはリモート通知をサポートしていないため、iOS でこのサンプルを探索するときは物理デバイスが必要です。 ただし、このチュートリアルを完了するために、Android と iOS の両方でアプリを実行する必要はありません。
この最初の原則の例の手順は、以前の経験なしで実行できます。 ただし、次の点を理解することでメリットが得られるでしょう。
- Apple Developer Portal.
- コアを
ASP.NET します。 - Google Firebase コンソールを
します。 - Microsoft Azure と Azure Notification Hubsを使用して iOS アプリにプッシュ通知を送信します。
- クロスプラットフォーム開発用の Flutter と
Dart をします。 - Android および iOS ネイティブ開発用の Kotlin と
Swift をします。
提供される手順は、macOSに固有です。 iOS の の側面をスキップすることで、Windows に従うことができます。
プッシュ通知サービスと Azure Notification Hub を設定する
このセクションでは、Firebase Cloud Messaging (FCM) と Apple Push Notification Services (APNS)を設定します。 次に、これらのサービスを操作するための通知ハブを作成して構成します。
Firebase プロジェクトを作成し、Android 用 Firebase Cloud Messaging を有効にする
Firebase コンソールにサインインします。
プロジェクト名として PushDemo 新しい Firebase プロジェクトを作成します。入力 手記
一意の名前が自動的に生成されます。 既定では、これは、指定した名前の小文字のバリアントと、ダッシュで区切られた生成された番号で構成されます。 グローバルに一意である場合は、これを変更できます。
プロジェクトを作成したら、[Firebase を Android アプリに追加] を選択します。
に追加する
[Add Firebase to your Android app]\(Firebase を Android アプリに追加する\) ページで、次の手順を実行します。
Android パッケージ名に、パッケージの名前を入力します。 例:
com.<organization_identifier>.<package_name>
.を指定する
[アプリ
登録] を選択します。 [google-services.json
ダウンロード] を選択します。 次に、後で使用するためにファイルをローカル フォルダーに保存し、[次 ] を選択します。 [次
選択します。 [続行 コンソール を選択する
手記
インストールの確認 チェックが に進む] ボタンが有効になっていない場合は、[この手順スキップのため、[コンソール 選択します。
Firebase コンソールで、プロジェクトの歯車を選択します。 次に、[プロジェクト設定]
選択します。 手記
google-services.json ファイルをダウンロードしていない場合は、このページでダウンロードできます。
上部にある [クラウド メッセージング] タブに切り替えます。 後で使用するために、サーバー キー をコピーして保存します。 この値を使用して、通知ハブを構成します。
をコピーする
プッシュ通知用に iOS アプリを登録する
iOS アプリにプッシュ通知を送信するには、Apple にアプリケーションを登録し、プッシュ通知にも登録します。
まだアプリを登録していない場合は、Apple デベロッパー センターの iOS プロビジョニング ポータル を参照します。 Apple ID を使用してポータルにサインインし、証明書、識別子、プロファイル &に移動し、[識別子]を選択します。 + をクリックして新しいアプリを登録します。
[新しい識別子の登録] 画面で、[アプリ ID] ラジオ ボタンを選択します。 次に、[続行]
選択します。 iOS プロビジョニング ポータル
新しいアプリの次の 3 つの値を更新し、[続行]
選択します。 説明: アプリのわかりやすい名前を入力します。
バンドル ID: フォームのバンドル ID 入力します。<organization_identifier>。アプリ配布ガイドのに記載されている<product_name>。 次のスクリーンショットでは、
mobcat
値が組織識別子として使用され、PushDemo 値が製品名として使用されています。iOS プロビジョニング ポータル
プッシュ通知の: [機能] セクションの [プッシュ通知] オプションをオンにします。
を登録する
このアクションにより、アプリ ID が生成され、情報の確認が要求されます。 [続行]
選択し、[ 登録] を選択して新しいアプリ ID を確認します。 を確認する
の登録
選択すると、新しいアプリ ID が [ 証明書、識別子 & プロファイル] ページに行項目として表示されます。
[証明書] の [識別子 & プロファイル] ページの [識別子]で、作成したアプリ ID の行項目を見つけます。 次に、その行を選択して、[アプリ ID 構成の編集] 画面
表示します。
Notification Hubs の証明書の作成
Apple Push Notification Services (APNS) で通知ハブを操作できるようにするために証明書が必要であり、次の 2 つの方法のいずれかで提供できます。
Notification Hub に直接アップロードできる p12 プッシュ証明書の作成 (元のアプローチ) トークン ベースの認証 に使用できる p8 証明書の作成 (新しい推奨されるアプローチ)
新しいアプローチには、APNSの
オプション 1: Notification Hub に直接アップロードできる p12 プッシュ証明書を作成する
Mac で、キーチェーン アクセス ツールを実行します。 Utilities フォルダーまたはスタート パッドの Other フォルダーから開くことができます。
キーチェーン アクセス
選択し、証明書アシスタント 展開して、証明機関から証明書 要求を選択します。 キーチェーン アクセスを使用して新しい証明書 を要求する
手記
既定では、キーチェーン アクセスはリスト内の最初の項目を選択します。 これは、証明書 カテゴリに属していて、Apple Worldwide Developer Relations 証明機関 がリストの最初の項目でない場合に問題になる可能性があります。 CSR (証明書署名要求) を生成する前に、キー以外の項目があることを確認するか、Apple Worldwide Developer Relations 証明機関 キー
が選択されていることを確認します。 ユーザーの電子メール アドレス を選択し、共通名の の値を入力し、[ディスクに保存]指定していることを確認し、[続行] 選択します。 CA 電子メール アドレス は不要であるため、空白のままにします。 [名前を付けて保存]に
証明書署名要求 (CSR) ファイルの名前 入力し、[場所] で場所 選択し、[保存] 選択します。 このアクションにより、選択した場所に CSR ファイル が保存されます。 既定の場所はデスクトップ
です。 ファイルに対して選択した場所を覚えておいてください。 iOS プロビジョニング ポータル の証明書、識別子 & プロファイル ページに戻り、チェックされた [プッシュ通知 ] オプションまで下にスクロールし、[ の構成]選択して証明書を作成します。 [Apple Push Notification service TLS/SSL Certificates] ウィンドウが表示されます。 [
開発 TLS/SSL 証明書 ] セクションの下にある [証明書 の作成] ボタンを選択します。 新しい証明書 の作成画面が表示されます。
手記
このチュートリアルでは、開発証明書を使用します。 運用環境の証明書を登録する場合も同じプロセスが使用されます。 通知を送信するときは、必ず同じ種類の証明書を使用してください。
[ファイル
選択] を選択し、 CSR ファイルを 保存した場所を参照し、証明書名をダブルクリックして読み込みます。 次に、[続行]選択します。 ポータルで証明書を作成した後、ダウンロード ボタンを選択します。 証明書を保存し、保存先の場所を覚えておいてください。
証明書がダウンロードされ、ダウンロード フォルダーにコンピューターに保存されます。
手記
既定では、ダウンロードした開発証明書には aps_development.cerという名前が付けられます。
ダウンロードしたプッシュ証明書の aps_development.cerをダブルクリックします。 この操作により、次の図に示すように、キーチェーンに新しい証明書がインストールされます。
新しい証明書 を示すキーチェーン アクセス証明書の一覧を
する 手記
証明書内の名前は異なる場合がありますが、名前の前に Apple Development iOS Push Services が付き、適切なバンドル識別子が関連付けられます。
キーチェーン アクセスで、コントロール + 証明書 カテゴリで作成した新しいプッシュ証明書の をクリックします。 [エクスポート
選択し、ファイルに名前を付け、 p12 形式を選択して、[保存]選択します。 証明書をパスワードで保護することもできますが、パスワードは省略可能です。 パスワードの作成 バイパスする場合は、[OK] をクリックします。 エクスポートした p12 証明書のファイル名と場所を書き留めます。 これらは、APN での認証を有効にするために使用されます。
手記
p12 ファイルの名前と場所は、このチュートリアルで説明されているものとは異なる場合があります。
オプション 2: トークン ベースの認証に使用できる p8 証明書を作成する
次の詳細を書き留めます。
- アプリ ID プレフィックス (チーム ID)
- バンドル ID
証明書、識別子 & プロファイル に戻り、[キーの] をクリックします。 手記
APNS用に構成されたキーが既にある場合は、作成直後にダウンロードした p8 証明書を再利用できます。 その場合は、
5 までの 3手順を無視できます。 [+] ボタン (または [キーの作成] ボタン) をクリックして、新しいキーを作成します。
適切な キー名 値を指定し、Apple Push Notifications サービス (APNS) オプションをオンにして、[続行] をクリックし、次の画面で [登録] をクリックします。
[
ダウンロード] をクリックし、 p8 ファイル (プレフィックスはAuthKey_ ) をセキュリティで保護されたローカル ディレクトリに移動し、[完了]クリック 。手記
p8 ファイルは安全な場所に保管してください (バックアップを保存してください)。 キーをダウンロードした後は、サーバーのコピーが削除されるため、再ダウンロードできません。
キーで、作成したキー (または代わりに使用することを選択した場合は既存のキー) をクリックします。
キー ID 値を書き留めます。
Visual Studio Codeなど、任意の適切なアプリケーションで p8 証明書を開きます。 キーの値 (----- BEGIN 秘密キー----- と ----- END 秘密キー-----) を書き留めます。
-----秘密キーを使用します-----
<key_value>
秘密キーを-----します-----手記
これは、後で Notification Hubを構成するために使用される
トークン値 です。
これらの手順の最後には、「APNS 情報を使用して通知ハブを構成する 後で使用するための次の情報が必要です。
- チーム ID (手順 1 を参照)
- バンドル ID (手順 1 を参照)
- キー ID (手順 7 を参照)
- トークン値 (手順 8 で取得した p8 キー値)
アプリのプロビジョニング プロファイルを作成する
iOS プロビジョニング ポータルに戻り、[証明書 ]、[識別子]、[プロファイル] &の順に選択し、左側のメニューから [プロファイル] を選択し、+ を選択して新しいプロファイルを作成します。 [新しいプロビジョニング プロファイルの登録] 画面が表示されます。
プロビジョニング プロファイル
を選択し、[続行][開発 ] の下にある [iOS アプリ開発選択します。 次に、[
アプリ ID ] ドロップダウン リストから作成したアプリ ID を選択し、[続行]選択します。 を選択する
[証明書の選択 ] ウィンドウで、コード署名に使用する開発証明書を選択し、[続行]選択します。 手記
この証明書は、前の手順
で作成したプッシュ証明書ではありません。 これは開発証明書です。 存在しない場合は、このチュートリアルの 前提条件 であるため、作成する必要があります。 開発者証明書は、 Apple Developer Portal 、xcode、または Visual Studio使用して作成できます。 証明書、識別子 & プロファイル ページに戻り、左側のメニューから プロファイル を選択し、+ を選択して新しいプロファイルを作成します。 [新しいプロビジョニング プロファイルの登録] 画面が表示されます。
証明書の選択 ウィンドウで、作成した開発証明書を選択します。 次に、[続行]
選択します。 次に、テストに使用するデバイスを選択し、[続行]
選択します。 最後に、[プロビジョニング プロファイル名]でプロファイルの名前
選択し、[の生成] 選択します。 新しいプロビジョニング プロファイルが作成されたら、[のダウンロード]
選択します。 保存先の場所を覚えておいてください。 プロビジョニング プロファイルの場所を参照し、それをダブルクリックして開発用コンピューターにインストールします。
通知ハブを作成する
このセクションでは、通知ハブを作成し、APNSを使用して認証を構成します。 p12 プッシュ証明書またはトークン ベースの認証を使用できます。 既に作成した通知ハブを使用する場合は、手順 5 に進むことができます。
Azureにサインインします。
[リソース
作成] をクリックし、Notification Hub 検索して選択し、[の作成] クリックします。 次のフィールドを更新し、の作成
クリックします。 基本的な詳細 を
する サブスクリプション: ドロップダウン リストからターゲット サブスクリプション を選択します
リソース グループ: 新しい リソース グループ を作成する (または既存のものを選択する)名前空間の詳細 を
する Notification Hub 名前空間:Notification Hub 名前空間のグローバルに一意の名前を入力します
手記
このフィールドに 新しい の作成オプションが選択されていることを確認します。
通知ハブの詳細 を
する 通知ハブ:Notification Hub の名前を入力します
場所: ドロップダウン リストから適切な場所を選択します
価格レベル: 既定の Free オプションを保持する手記
Free レベルのハブの最大数に達していない限り。
Notification Hub がプロビジョニングされたら、そのリソースに移動します。
新しい Notification Hubに移動します。
一覧から
アクセス ポリシー を選択します ([manage] の下)。 ポリシー名の 値と、対応する 接続文字列 値を書き留めます。
APNS 情報を使用して Notification Hub を構成する
手記
ストアからアプリを購入したユーザーにプッシュ通知を送信する場合にのみ、アプリケーション モードの の 運用 を使用します。
オプション 1: .p12 プッシュ証明書の使用
[証明書
選択します。 ファイル アイコンを選択します。
先ほどエクスポートした .p12 ファイルを選択し、[を開く]
選択します。 必要に応じて、正しいパスワードを指定します。
サンドボックス モード
選択します。 [保存]を選択します。
オプション 2: トークン ベースの認証を使用する
[トークン
選択します。 前に取得した次の値を入力します。
- キー ID
- バンドル ID
- チーム ID
- トークンの
[サンドボックス
選択します。 [保存]を選択します。
FCM 情報を使用して通知ハブを構成する
- 左側 メニューの [設定] セクションで Google (GCM/FCM) を選択します。
- Google Firebase Consoleから、サーバー キー 入力します。
- ツール バー [保存] を選択します。
ASP.NET Core Web API バックエンド アプリケーションを作成する
このセクションでは、ASP.NET Core Web API バックエンドを作成して、デバイス登録 と Flutter モバイル アプリへの通知の送信を処理します。
Web プロジェクトを作成する
Visual Studio で、[ファイル][新しいソリューション] 選択します。 [.NET Core
App ASP.NET Core API Next を] を選択します。 [新しい ASP.NET Core Web API の構成] ダイアログで、.NET Core 3.1のターゲット フレームワーク選択します。 プロジェクト名 に「PushDemoApi入力し、[作成] 選択します。 デバッグを開始 (コマンド + Enter) して、テンプレート 化されたアプリをテストします。
手記
テンプレートアプリは、launchUrlとして WeatherForecastController を使用するように構成されています。 これは、プロパティ>launchSettings.jsonで設定されます。
無効な開発証明書が見つかりました メッセージが表示された場合:
[はい] をクリックして、"dotnet dev-certs https" ツールを実行してこれを修正することに同意します。 "dotnet dev-certs https" ツールでは、証明書のパスワードとキーチェーンのパスワードの入力を求められます。
インストールを し、新しい証明書を信頼するように求められたら、[はい] をクリックし、キーチェーンのパスワードを入力します。
Controllers フォルダーを展開し、WeatherForecastController.csを削除します。
WeatherForecast.csを削除します。
Secret Manager ツールを使用してローカル構成値を設定します。 シークレットをソリューションから切り離すことで、最終的にソース管理が行われることはありません。 ターミナル
開き、プロジェクト ファイルのディレクトリに移動し、次のコマンドを実行します。 dotnet user-secrets init dotnet user-secrets set "NotificationHub:Name" <value> dotnet user-secrets set "NotificationHub:ConnectionString" <value>
プレースホルダーの値を、独自の通知ハブ名と接続文字列の値に置き換えます。 通知ハブ の作成セクション
でメモしておきます。 それ以外の場合は、Azure で検索できます。 NotificationHub:Name:
の概要の上部にある Essentials 概要の 名 を参照してください。NotificationHub:ConnectionString:
アクセス ポリシー の DefaultFullSharedAccessSignature のを参照してください 手記
運用環境のシナリオでは、Azure KeyVault を
して接続文字列を安全に格納するオプションを確認できます。 わかりやすくするために、シークレットは Azure App Service アプリケーション設定に追加されます。
API キーを使用してクライアントを認証する (省略可能)
API キーはトークンほど安全ではありませんが、このチュートリアルでは十分です。 API キーは、ASP.NET ミドルウェアを使用して簡単に構成できます。
API キー をローカル構成値に追加します。
dotnet user-secrets set "Authentication:ApiKey" <value>
手記
プレースホルダーの値を独自の値に置き換え、メモする必要があります。
コントロール ] をクリックし、[ の追加] メニューから [新しいフォルダーPushDemoApi プロジェクトの [を選択し、 フォルダー名 として認証 を使用して を追加] をクリックします。 コントロール ] をクリックし、[認証 フォルダーの [追加] メニューの [新しいファイル]< 選択します。 [全般]
[空のクラス] 選択し、[ 名] にApiKeyAuthOptions.cs を入力し、[新規]クリック 、次の実装を追加します。using Microsoft.AspNetCore.Authentication; namespace PushDemoApi.Authentication { public class ApiKeyAuthOptions : AuthenticationSchemeOptions { public const string DefaultScheme = "ApiKey"; public string Scheme => DefaultScheme; public string ApiKey { get; set; } } }
ApiKeyAuthHandler.csという名前 の Authentication フォルダーに空のクラス を追加し、次の実装を追加します。
using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace PushDemoApi.Authentication { public class ApiKeyAuthHandler : AuthenticationHandler<ApiKeyAuthOptions> { const string ApiKeyIdentifier = "apikey"; public ApiKeyAuthHandler( IOptionsMonitor<ApiKeyAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) {} protected override Task<AuthenticateResult> HandleAuthenticateAsync() { string key = string.Empty; if (Request.Headers[ApiKeyIdentifier].Any()) { key = Request.Headers[ApiKeyIdentifier].FirstOrDefault(); } else if (Request.Query.ContainsKey(ApiKeyIdentifier)) { if (Request.Query.TryGetValue(ApiKeyIdentifier, out var queryKey)) key = queryKey; } if (string.IsNullOrWhiteSpace(key)) return Task.FromResult(AuthenticateResult.Fail("No api key provided")); if (!string.Equals(key, Options.ApiKey, StringComparison.Ordinal)) return Task.FromResult(AuthenticateResult.Fail("Invalid api key.")); var identities = new List<ClaimsIdentity> { new ClaimsIdentity("ApiKeyIdentity") }; var ticket = new AuthenticationTicket( new ClaimsPrincipal(identities), Options.Scheme); return Task.FromResult(AuthenticateResult.Success(ticket)); } } }
手記
認証ハンドラー は、スキームの動作 (この場合はカスタム API キー スキーム) を実装する型です。
別の Empty クラス を ApiKeyAuthenticationBuilderExtensions.csという名前の Authentication フォルダーに追加し、次の実装を追加します。
using System; using Microsoft.AspNetCore.Authentication; namespace PushDemoApi.Authentication { public static class AuthenticationBuilderExtensions { public static AuthenticationBuilder AddApiKeyAuth( this AuthenticationBuilder builder, Action<ApiKeyAuthOptions> configureOptions) { return builder .AddScheme<ApiKeyAuthOptions, ApiKeyAuthHandler>( ApiKeyAuthOptions.DefaultScheme, configureOptions); } } }
手記
この拡張メソッドは、ミドルウェア構成コードを簡略化 Startup.cs、より読みやすく、一般的に従いやすくします。
Startup.csで、ConfigureServices メソッドを更新して、サービスの呼び出しの下で API キー認証を構成します。AddControllers メソッド。
using PushDemoApi.Authentication; using PushDemoApi.Models; using PushDemoApi.Services; public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = ApiKeyAuthOptions.DefaultScheme; options.DefaultChallengeScheme = ApiKeyAuthOptions.DefaultScheme; }).AddApiKeyAuth(Configuration.GetSection("Authentication").Bind); }
引き続き
Startup.cs で、Configure メソッドを更新して、UseAuthentication を呼び出し、アプリのIApplicationBuilder で UseAuthorization 拡張メソッドをします。 これらのメソッドは、UseRouting 後、およびアプリの前 呼び出されていることを確認します。UseEndpoints .public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
手記
UseAuthentication
を呼び出すと、( ConfigureServices から) 以前に登録された認証スキームを使用するミドルウェアが登録されます。 これは、認証されるユーザーに依存するミドルウェアの前に呼び出す必要があります。
依存関係の追加とサービスの構成
ASP.NET Core では、
バックエンド操作 に通知ハブと
Control + Dependencies フォルダーの [] をクリックし、[NuGet パッケージの管理]選択します。..。
Microsoft.Azure.NotificationHubs を検索し、チェックされていることを確認します。
[パッケージ
追加] をクリックし、ライセンス条項 同意するように求められたら[ に同意する] をクリックします。コントロール ] をクリックし、[PushDemoApi プロジェクトの [の追加] メニューから [新しいフォルダー 選択し、 フォルダー名 として [モデル] を使用して を追加をクリックします。 コントロール ] をクリックし、[ の追加] メニューModels フォルダーの [から [新しいファイル を選択します。 [全般]
[空のクラス] 選択し、[ 名] にPushTemplates.cs を入力し、[新規] をクリック 次の実装を追加します。namespace PushDemoApi.Models { public class PushTemplates { public class Generic { public const string Android = "{ \"notification\": { \"title\" : \"PushDemo\", \"body\" : \"$(alertMessage)\"}, \"data\" : { \"action\" : \"$(alertAction)\" } }"; public const string iOS = "{ \"aps\" : {\"alert\" : \"$(alertMessage)\"}, \"action\" : \"$(alertAction)\" }"; } public class Silent { public const string Android = "{ \"data\" : {\"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\"} }"; public const string iOS = "{ \"aps\" : {\"content-available\" : 1, \"apns-priority\": 5, \"sound\" : \"\", \"badge\" : 0}, \"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\" }"; } } }
手記
このクラスには、このシナリオで必要な汎用通知とサイレント通知のトークン化された通知ペイロードが含まれています。 ペイロードは、サービスを介して既存のインストールを更新することなく実験できるように、インストール の外部で定義されます。 この方法でのインストールの変更の処理は、このチュートリアルでは範囲外です。 運用環境では、カスタム テンプレート
することを検討してください。 別の 空のクラス を DeviceInstallation.csという名前の Models フォルダーに追加し、次の実装を追加します。
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace PushDemoApi.Models { public class DeviceInstallation { [Required] public string InstallationId { get; set; } [Required] public string Platform { get; set; } [Required] public string PushChannel { get; set; } public IList<string> Tags { get; set; } = Array.Empty<string>(); } }
別の 空のクラス を NotificationRequest.csという名前の Models フォルダーに追加し、次の実装を追加します。
using System; namespace PushDemoApi.Models { public class NotificationRequest { public string Text { get; set; } public string Action { get; set; } public string[] Tags { get; set; } = Array.Empty<string>(); public bool Silent { get; set; } } }
NotificationHubOptions.csという名前 の Models フォルダーに空のクラス をもう 1 つ追加し、次の実装を追加します。
using System.ComponentModel.DataAnnotations; namespace PushDemoApi.Models { public class NotificationHubOptions { [Required] public string Name { get; set; } [Required] public string ConnectionString { get; set; } } }
Servicesと呼ばれる新しいフォルダーを PushDemoApi プロジェクトに追加します。
空のインターフェイス を INotificationService.csという名前の Services フォルダーに追加し、次の実装を追加します。
using System.Threading; using System.Threading.Tasks; using PushDemoApi.Models; namespace PushDemoApi.Services { public interface INotificationService { Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token); Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token); Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token); } }
空のクラス を NotificationHubsService.csという名前の Services フォルダーに追加し、次のコードを追加して、INotificationService インターフェイスを実装します。
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.NotificationHubs; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using PushDemoApi.Models; namespace PushDemoApi.Services { public class NotificationHubService : INotificationService { readonly NotificationHubClient _hub; readonly Dictionary<string, NotificationPlatform> _installationPlatform; readonly ILogger<NotificationHubService> _logger; public NotificationHubService(IOptions<NotificationHubOptions> options, ILogger<NotificationHubService> logger) { _logger = logger; _hub = NotificationHubClient.CreateClientFromConnectionString( options.Value.ConnectionString, options.Value.Name); _installationPlatform = new Dictionary<string, NotificationPlatform> { { nameof(NotificationPlatform.Apns).ToLower(), NotificationPlatform.Apns }, { nameof(NotificationPlatform.Fcm).ToLower(), NotificationPlatform.Fcm } }; } public async Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token) { if (string.IsNullOrWhiteSpace(deviceInstallation?.InstallationId) || string.IsNullOrWhiteSpace(deviceInstallation?.Platform) || string.IsNullOrWhiteSpace(deviceInstallation?.PushChannel)) return false; var installation = new Installation() { InstallationId = deviceInstallation.InstallationId, PushChannel = deviceInstallation.PushChannel, Tags = deviceInstallation.Tags }; if (_installationPlatform.TryGetValue(deviceInstallation.Platform, out var platform)) installation.Platform = platform; else return false; try { await _hub.CreateOrUpdateInstallationAsync(installation, token); } catch { return false; } return true; } public async Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token) { if (string.IsNullOrWhiteSpace(installationId)) return false; try { await _hub.DeleteInstallationAsync(installationId, token); } catch { return false; } return true; } public async Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token) { if ((notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Action)) || (!notificationRequest.Silent && (string.IsNullOrWhiteSpace(notificationRequest?.Text)) || string.IsNullOrWhiteSpace(notificationRequest?.Action))) return false; var androidPushTemplate = notificationRequest.Silent ? PushTemplates.Silent.Android : PushTemplates.Generic.Android; var iOSPushTemplate = notificationRequest.Silent ? PushTemplates.Silent.iOS : PushTemplates.Generic.iOS; var androidPayload = PrepareNotificationPayload( androidPushTemplate, notificationRequest.Text, notificationRequest.Action); var iOSPayload = PrepareNotificationPayload( iOSPushTemplate, notificationRequest.Text, notificationRequest.Action); try { if (notificationRequest.Tags.Length == 0) { // This will broadcast to all users registered in the notification hub await SendPlatformNotificationsAsync(androidPayload, iOSPayload, token); } else if (notificationRequest.Tags.Length <= 20) { await SendPlatformNotificationsAsync(androidPayload, iOSPayload, notificationRequest.Tags, token); } else { var notificationTasks = notificationRequest.Tags .Select((value, index) => (value, index)) .GroupBy(g => g.index / 20, i => i.value) .Select(tags => SendPlatformNotificationsAsync(androidPayload, iOSPayload, tags, token)); await Task.WhenAll(notificationTasks); } return true; } catch (Exception e) { _logger.LogError(e, "Unexpected error sending notification"); return false; } } string PrepareNotificationPayload(string template, string text, string action) => template .Replace("$(alertMessage)", text, StringComparison.InvariantCulture) .Replace("$(alertAction)", action, StringComparison.InvariantCulture); Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, CancellationToken token) { var sendTasks = new Task[] { _hub.SendFcmNativeNotificationAsync(androidPayload, token), _hub.SendAppleNativeNotificationAsync(iOSPayload, token) }; return Task.WhenAll(sendTasks); } Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, IEnumerable<string> tags, CancellationToken token) { var sendTasks = new Task[] { _hub.SendFcmNativeNotificationAsync(androidPayload, tags, token), _hub.SendAppleNativeNotificationAsync(iOSPayload, tags, token) }; return Task.WhenAll(sendTasks); } } }
手記
SendTemplateNotificationAsync に提供されるタグ式は、20 個のタグに制限されています。 ほとんどの演算子では 6 に制限されていますが、この場合、式に含まれるのは OR (||) のみです。 要求に 20 個を超えるタグがある場合は、複数の要求に分割する必要があります。 詳細については、ルーティングとタグ式 ドキュメントを参照してください。
Startup.cs で、ConfigureServices メソッドを更新して、NotificationHubsService を INotificationServiceのシングルトン実装として追加します。 using PushDemoApi.Models; using PushDemoApi.Services; public void ConfigureServices(IServiceCollection services) { ... services.AddSingleton<INotificationService, NotificationHubService>(); services.AddOptions<NotificationHubOptions>() .Configure(Configuration.GetSection("NotificationHub").Bind) .ValidateDataAnnotations(); }
通知 API を作成する
コントロール + Controllers フォルダーの [] をクリックし、[ 追加] メニューから [新しいファイル 選択します。..。
[コア
Web API コントローラー クラス ASP.NET 選択し、 名 に「NotificationsController」と入力し、[新しい ] をクリックします。 手記
Visual Studio 2019でフォローしている場合は、読み取り/書き込みアクションを含む API コントローラー テンプレートを選択します。
ファイルの先頭に次の名前空間を追加します。
using System.ComponentModel.DataAnnotations; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PushDemoApi.Models; using PushDemoApi.Services;
ControllerBase から派生し、ApiController 属性で修飾されるように、テンプレート化されたコントローラーを更新します。
[ApiController] [Route("api/[controller]")] public class NotificationsController : ControllerBase { // Templated methods here }
手記
Controller 基本クラスはビューのサポートを提供しますが、この場合は必要ないため、代わりに ControllerBase使用できます。 Visual Studio 2019でフォローしている場合は、この手順をスキップできます。 API キー セクションを使用してクライアントを認証する
を完了する場合は、 NotificationsController をAuthorize 属性で修飾する必要があります。[Authorize]
INotificationService の登録済みインスタンスを引数として受け入れるようにコンストラクターを更新し、読み取り専用メンバーに割り当てます。
readonly INotificationService _notificationService; public NotificationsController(INotificationService notificationService) { _notificationService = notificationService; }
launchSettings.json (プロパティ フォルダー内) で、launchUrl を
weatherforecast
から api/notifications に変更し、RegistrationsControllerRoute 属性で指定された URL に一致させます。デバッグを開始します (コマンド + Enter) して、アプリが新しい NotificationsController で動作していることを検証し、401 Unauthorized 状態を返します。
手記
Visual Studio がブラウザーでアプリを自動的に起動しない場合があります。 Postman
使用して、この時点から API をテストします。 新しい
タブで、要求を GETPostman 設定します。 applicationUrl プレースホルダーを、 プロパティ launchSettings.json にある httpsapplicationUrl に置き換えて、次のアドレスを入力します。<applicationUrl>/api/notifications
手記
既定のプロファイルの場合、applicationUrl は 'https://localhost:5001' にする必要があります。 IIS (Windows Visual Studio 2019 の既定値) を使用している場合は、代わりに iisSettings 項目で指定された applicationUrl を使用する必要があります。 アドレスが正しくない場合は、404 応答を受け取ります。
API キー セクションを使用してクライアントを認証する
を完了する場合は、 apikey 値を含めるように要求ヘッダーを構成してください。鍵 価値 apikey <your_api_key> [送信] ボタンをクリックします。
手記
200 OK 状態を受け取り、一部の JSON コンテンツが表示されます。
SSL 証明書検証 警告を受け取った場合は、[設定] で Postman 設定SSL 証明書の要求検証をオフにすることができます。 NotificationsController.cs のテンプレート化されたクラス メソッドを次のコードに置き換えます。
[HttpPut] [Route("installations")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<IActionResult> UpdateInstallation( [Required]DeviceInstallation deviceInstallation) { var success = await _notificationService .CreateOrUpdateInstallationAsync(deviceInstallation, HttpContext.RequestAborted); if (!success) return new UnprocessableEntityResult(); return new OkResult(); } [HttpDelete()] [Route("installations/{installationId}")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<ActionResult> DeleteInstallation( [Required][FromRoute]string installationId) { var success = await _notificationService .DeleteInstallationByIdAsync(installationId, CancellationToken.None); if (!success) return new UnprocessableEntityResult(); return new OkResult(); } [HttpPost] [Route("requests")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)] public async Task<IActionResult> RequestPush( [Required]NotificationRequest notificationRequest) { if ((notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Action)) || (!notificationRequest.Silent && string.IsNullOrWhiteSpace(notificationRequest?.Text))) return new BadRequestResult(); var success = await _notificationService .RequestNotificationAsync(notificationRequest, HttpContext.RequestAborted); if (!success) return new UnprocessableEntityResult(); return new OkResult(); }
API アプリを作成する
これで、バックエンド サービスをホストするための API AppAzure App Service を作成します。
Azure portalにサインインします。
[リソース
作成] をクリックし、API App 検索して選択し、[の作成] クリックします。 次のフィールドを更新し、の作成
クリックします。 アプリ名:
API アプリ のグローバルに一意の名前を入力しますサブスクリプション:
通知ハブを作成したのと同じターゲット サブスクリプション を選択します。リソース グループ:
通知ハブを作成したのと同じ リソース グループ を選択します。App Service プラン/場所:
新しい App Service プラン を作成する手記
既定のオプションから、SSL サポートを含むプランに変更します。 それ以外の場合は、モバイル アプリを操作するときに適切な手順を実行して、http 要求
ブロックされないようにする必要があります。 Application Insights:
推奨されるオプション (その名前を使用して新しいリソースが作成されます) をそのまま使用するか、既存のリソースを選択します。API アプリ がプロビジョニングされたら、そのリソースに移動します。
の [概要]の上部にある Essentials の概要に、URL プロパティを書き留めます。 この URL は、このチュートリアルの後半で使用する バックエンド エンドポイントの です。
手記
この URL では、前に指定した API アプリ名が、
https://<app_name>.azurewebsites.net
形式で使用されます。一覧から 構成 を選択します ([設定])。
以下の各設定で、[新しいアプリケーション設定
をクリックして 名の と値の を入力し、[OK]クリックします。 名前 価値 Authentication:ApiKey
<api_key_value> NotificationHub:Name
<hub_name_value> NotificationHub:ConnectionString
<hub_connection_string_value> 手記
これらは、ユーザー設定で以前に定義した設定と同じです。 これらをコピーできる必要があります。
Authentication:ApiKey 設定は、API キー セクションを使用してクライアントを認証するを完了することを選択した場合にのみ必要です。 運用環境のシナリオでは、Azure KeyVault などのオプションを確認できます。 これらは、この場合にわかりやすくするためにアプリケーション設定として追加されています。 すべてのアプリケーション設定が追加されたら、
保存 をクリックし、[続行]します。
バックエンド サービスを発行する
次に、API アプリにアプリをデプロイして、すべてのデバイスからアクセスできるようにします。
手記
次の手順は、Visual Studio for Macに固有です。 Windows Visual Studio 2019 でフォローしている場合、発行フローは異なります。 Windows
まだ行っていない場合は、構成を Debug から Release に変更します。
Control クリックし、[PushDemoApi プロジェクト発行] メニューから [Azure に発行] 。選択 認証フローに従う必要がある場合は、それに従います。 前の手順で使用したアカウントを使用 、API アプリの セクションを作成します。
前に作成した
Azure App Service API アプリ を発行ターゲットとして一覧から選択し、[発行] をクリックします。
ウィザードが完了すると、アプリが Azure に発行され、アプリが開きます。 まだ行っていない場合は、URL を書き留めておきます。 この URL は、このチュートリアルの後半で使用する バックエンド エンドポイントの です。
発行された API の検証
新しいタブを開き、PUTPostman 要求を設定し、以下のアドレスを入力します。 プレースホルダーを、前の バックエンド サービスの セクションでメモしたベース アドレスに置き換えます。 https://<app_name>.azurewebsites.net/api/notifications/installations
手記
ベース アドレスは、
https://<app_name>.azurewebsites.net/
の形式にする必要がありますAPI キー セクションを使用してクライアントを認証する
を完了する場合は、 apikey 値を含めるように要求ヘッダーを構成してください。鍵 価値 apikey <your_api_key> Body のraw オプションを選択し、書式設定オプションの一覧から JSON選択し、JSON コンテンツ プレースホルダーを含めます。 {}
[
送信] をクリックします。 手記
サービスから 422 UnprocessableEntity 状態を受け取る必要があります。
手順 1 から 4 をもう一度実行しますが、今回は要求エンドポイントを指定して、400 Bad Request 応答を受け取ったかどうかを検証します。
https://<app_name>.azurewebsites.net/api/notifications/requests
手記
クライアント モバイル アプリからプラットフォーム固有の情報が必要になるため、有効な要求データを使用して API をテストすることはできません。
クロスプラットフォーム Flutter アプリケーションを作成する
このセクションでは、クロスプラットフォーム方式でプッシュ通知を実装する Flutter モバイル アプリケーションを構築します。
これにより、作成したバックエンド サービスを介して通知ハブを登録および登録解除できます。
アクションが指定され、アプリがフォアグラウンドにある場合、アラートが表示されます。 それ以外の場合、通知は通知センターに表示されます。
手記
通常、登録 (および登録解除) アクションは、明示的なユーザー 登録/登録解除の入力なしで、アプリケーション ライフサイクルの適切な時点 (または最初の実行エクスペリエンスの一部として) 実行します。 ただし、この例では、この機能をより簡単に調査およびテストできるようにするために、明示的なユーザー入力が必要になります。
Flutter ソリューションを作成する
Visual Studio Codeの新しいインスタンス
開きます。 コマンド パレット を開きます (Shift + コマンド + P)。
Flutter: New Project コマンドを選択し、Enter キー 押。
プロジェクト名 の push_demo を入力し、プロジェクトの場所を選択します。
メッセージが表示されたら、[パッケージの取得]
選択します。 コントロール をクリックし、Finderでkotlin フォルダー (srcメイン アプリの下) の 表示を選択します。 次に、子フォルダー (kotlin フォルダーの下) の名前をそれぞれ com
、<your_organization>
、およびpushdemo
に変更します。手記
Visual Studio Code テンプレートを使用する場合、これらのフォルダーは既定で com(の例 。 mobcatproject_name 組織 に使用されていると仮定すると、フォルダー構造は次のように表示されます。- kotlin
- com
- mobcat
- pushdemo
- mobcat
- com
- kotlin
Visual Studio Code に戻り、androidアプリの applicationId 値build.gradle に更新します。 手記
<your_organization> プレースホルダーには、独自の組織名を使用する必要があります。 たとえば、組織
mobcat を使用すると、com.mobcat.pushdemoのパッケージ名 値作成されます。 src
デバッグ 、srcmain 、およびsrc プロファイルの 、 AndroidManifest.xml ファイル内のパッケージ 属性を更新します。 値が前の手順で使用した applicationId 一致していることを確認します。<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.<your_organization>.pushdemo>"> ... </manifest>
メイン の src の下にあるAndroidManifest.xml ファイルの属性 更新して、PushDemoを します。 次に、 属性を のすぐ下に追加し、その値を false 設定します。 <application android:name="io.flutter.app.FlutterApplication" android:label="PushDemo" android:allowBackup="false" android:icon="@mipmap/ic_launcher"> ... </application>
app-level
build.gradle ファイル (androidapp build.gradle ) を開き、api 値を (29 を使用するように (android セクションから)compileSdkVersion を更新します。 次に、minSdkVersion と targetSdkVersiondefaultConfig セクションから)、それぞれ 26 と 29 に更新します。 手記
このチュートリアルでは、API レベル 26 以上
実行されているデバイスのみがサポートされていますが、古いバージョンを実行しているデバイスをサポートするように拡張することはできます。 コントロール をクリックし、Xcodeで開くios フォルダの選択します。 Xcode
で、 ランナー (フォルダーではなく上部にあるxcodeproj ) をクリックします。 次に、ターゲットの ランナー を選択し、[全般] タブを選択します。All ビルド構成が選択された状態で、バンドル識別子の をcom.<your_organization>.PushDemo
に更新します。手記
<your_organization> プレースホルダーには、独自の組織名を使用する必要があります。 たとえば、組織
mobcat を使用すると、com.mobcat.PushDemoのバンドル識別子 値になります。 [Info.plist
クリック し、バンドル名 値を PushDemo更新します。 Xcode
閉じ、Visual Studio Code に戻ります。 Visual Studio Code に戻り、pubspec.yaml開き、 http とflutter_secure_storage Dart パッケージを依存関係として 追加します。 次に、ファイルを保存し、メッセージが表示されたら パッケージの取得 をクリックします。dependencies: flutter: sdk: flutter http: ^0.12.1 flutter_secure_storage: ^3.3.3
ターミナルで、ディレクトリを ios フォルダーに変更します (Flutter プロジェクトの場合)。 次に、ポッドの install コマンドを実行して、新しいポッド (flutter_secure_storage パッケージで必要) をインストールします。
コントロール をクリックし、ファイル名としてlib フォルダーのmain_page.dart を使用してメニューから [新しいファイル]選択します。 次に、次のコードを追加します。 import 'package:flutter/material.dart'; class MainPage extends StatefulWidget { @override _MainPageState createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[], ) ) ); } }
main.dart
で、テンプレート化されたコードを次のように置き換えます。 import 'package:flutter/material.dart'; import 'package:push_demo/main_page.dart'; final navigatorKey = GlobalKey<NavigatorState>(); void main() => runApp(MaterialApp(home: MainPage(), navigatorKey: navigatorKey));
ターミナルで、各ターゲット プラットフォームでアプリをビルドして実行し、テンプレート化されたアプリがデバイスで実行されるようにテストします。 サポートされているデバイスが接続されていることを確認します。
flutter run
クロスプラットフォーム コンポーネントを実装する
コントロール をクリックし、lib フォルダのフォルダ名 としてモデル を使用してメニューから新しいフォルダ を選択します。コントロール をクリックし、ファイル名としてモデル フォルダーのdevice_installation.dart を使用してメニューから [新しいファイルの を] を選択します。 次に、次のコードを追加します。 class DeviceInstallation { final String deviceId; final String platform; final String token; final List<String> tags; DeviceInstallation(this.deviceId, this.platform, this.token, this.tags); DeviceInstallation.fromJson(Map<String, dynamic> json) : deviceId = json['installationId'], platform = json['platform'], token = json['pushChannel'], tags = json['tags']; Map<String, dynamic> toJson() => { 'installationId': deviceId, 'platform': platform, 'pushChannel': token, 'tags': tags, }; }
push_demo_action.dart というフォルダー 、この例でサポートされているアクションの列挙体を定義します。モデルに新しいファイルを追加 enum PushDemoAction { actionA, actionB, }
services という名前の新しいフォルダーをプロジェクトに追加、次の実装で device_installation_service.dart という名前のフォルダーに新しいファイルを追加します。
import 'package:flutter/services.dart'; class DeviceInstallationService { static const deviceInstallation = const MethodChannel('com.<your_organization>.pushdemo/deviceinstallation'); static const String getDeviceIdChannelMethod = "getDeviceId"; static const String getDeviceTokenChannelMethod = "getDeviceToken"; static const String getDevicePlatformChannelMethod = "getDevicePlatform"; Future<String> getDeviceId() { return deviceInstallation.invokeMethod(getDeviceIdChannelMethod); } Future<String> getDeviceToken() { return deviceInstallation.invokeMethod(getDeviceTokenChannelMethod); } Future<String> getDevicePlatform() { return deviceInstallation.invokeMethod(getDevicePlatformChannelMethod); } }
手記
<your_organization> プレースホルダーには、独自の組織名を使用する必要があります。 たとえば、
mobcat を組織として使用すると、com.mobcat.pushdemo/deviceinstallationのMethodChannel 名作成されます。 このクラスは、基になるネイティブ プラットフォームとの連携をカプセル化して、必要なデバイス インストールの詳細を取得します。 MethodChannel は、基になるネイティブ プラットフォームとの双方向の非同期通信を容易にします。 このチャネルに対応するプラットフォーム固有のチャネルは、後の手順で作成されます。
次の実装を使用して、notification_action_service.dart という名前の別のファイルをそのフォルダーに追加します。
import 'package:flutter/services.dart'; import 'dart:async'; import 'package:push_demo/models/push_demo_action.dart'; class NotificationActionService { static const notificationAction = const MethodChannel('com.<your_organization>.pushdemo/notificationaction'); static const String triggerActionChannelMethod = "triggerAction"; static const String getLaunchActionChannelMethod = "getLaunchAction"; final actionMappings = { 'action_a' : PushDemoAction.actionA, 'action_b' : PushDemoAction.actionB }; final actionTriggeredController = StreamController.broadcast(); NotificationActionService() { notificationAction .setMethodCallHandler(handleNotificationActionCall); } Stream get actionTriggered => actionTriggeredController.stream; Future<void> triggerAction({action: String}) async { if (!actionMappings.containsKey(action)) { return; } actionTriggeredController.add(actionMappings[action]); } Future<void> checkLaunchAction() async { final launchAction = await notificationAction.invokeMethod(getLaunchActionChannelMethod) as String; if (launchAction != null) { triggerAction(action: launchAction); } } Future<void> handleNotificationActionCall(MethodCall call) async { switch (call.method) { case triggerActionChannelMethod: return triggerAction(action: call.arguments as String); default: throw MissingPluginException(); break; } } }
手記
これは、厳密に型指定された列挙型を使用してクロスプラットフォーム方式で処理できるように、通知アクションの処理を一元化するための簡単なメカニズムとして使用されます。 このサービスを使用すると、基になるネイティブ プラットフォームは、通知ペイロードでアクションが指定されたときにアクションをトリガーできます。 また、一般的なコードでは、Flutter が処理する準備ができたら、アプリケーションの起動時にアクションが指定されたかどうかを振り返って確認することもできます。 たとえば、通知センターからの通知をタップしてアプリを起動するとします。
次の実装を使用して、notification_registration_service.dart という サービス フォルダーに新しいファイルを追加します。
import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import 'package:push_demo/services/device_installation_service.dart'; import 'package:push_demo/models/device_installation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class NotificationRegistrationService { static const notificationRegistration = const MethodChannel('com.<your_organization>.pushdemo/notificationregistration'); static const String refreshRegistrationChannelMethod = "refreshRegistration"; static const String installationsEndpoint = "api/notifications/installations"; static const String cachedDeviceTokenKey = "cached_device_token"; static const String cachedTagsKey = "cached_tags"; final deviceInstallationService = DeviceInstallationService(); final secureStorage = FlutterSecureStorage(); String baseApiUrl; String apikey; NotificationRegistrationService(this.baseApiUrl, this.apikey) { notificationRegistration .setMethodCallHandler(handleNotificationRegistrationCall); } String get installationsUrl => "$baseApiUrl$installationsEndpoint"; Future<void> deregisterDevice() async { final cachedToken = await secureStorage.read(key: cachedDeviceTokenKey); final serializedTags = await secureStorage.read(key: cachedTagsKey); if (cachedToken == null || serializedTags == null) { return; } var deviceId = await deviceInstallationService.getDeviceId(); if (deviceId.isEmpty) { throw "Unable to resolve an ID for the device."; } var response = await http .delete("$installationsUrl/$deviceId", headers: {"apikey": apikey}); if (response.statusCode != 200) { throw "Deregister request failed: ${response.reasonPhrase}"; } await secureStorage.delete(key: cachedDeviceTokenKey); await secureStorage.delete(key: cachedTagsKey); } Future<void> registerDevice(List<String> tags) async { try { final deviceId = await deviceInstallationService.getDeviceId(); final platform = await deviceInstallationService.getDevicePlatform(); final token = await deviceInstallationService.getDeviceToken(); final deviceInstallation = DeviceInstallation(deviceId, platform, token, tags); final response = await http.put(installationsUrl, body: jsonEncode(deviceInstallation), headers: {"apikey": apikey, "Content-Type": "application/json"}); if (response.statusCode != 200) { throw "Register request failed: ${response.reasonPhrase}"; } final serializedTags = jsonEncode(tags); await secureStorage.write(key: cachedDeviceTokenKey, value: token); await secureStorage.write(key: cachedTagsKey, value: serializedTags); } on PlatformException catch (e) { throw e.message; } catch (e) { throw "Unable to register device: $e"; } } Future<void> refreshRegistration() async { final currentToken = await deviceInstallationService.getDeviceToken(); final cachedToken = await secureStorage.read(key: cachedDeviceTokenKey); final serializedTags = await secureStorage.read(key: cachedTagsKey); if (currentToken == null || cachedToken == null || serializedTags == null || currentToken == cachedToken) { return; } final tags = jsonDecode(serializedTags); return registerDevice(tags); } Future<void> handleNotificationRegistrationCall(MethodCall call) async { switch (call.method) { case refreshRegistrationChannelMethod: return refreshRegistration(); default: throw MissingPluginException(); break; } } }
手記
このクラスは、DeviceInstallationService の使用と、必要な登録、登録解除、更新の登録アクションを実行するためのバックエンド サービスへの要求をカプセル化します。
apiKey 引数は、API キー セクションを使用してクライアントを認証するを完了することを選択した場合にのみ必要です。 次の実装を使用して、config.dart という名前の
lib フォルダー新しいファイルを追加します。 class Config { static String apiKey = "API_KEY"; static String backendServiceEndpoint = "BACKEND_SERVICE_ENDPOINT"; }
手記
これは、アプリ シークレットを定義する簡単な方法として使用されます。 プレースホルダーの値を独自の値に置き換えます。 バックエンド サービスを構築するときに、これらをメモしておく必要があります。 API アプリの URL は
https://<api_app_name>.azurewebsites.net/
する必要があります。apiKey メンバーは、API キー セクションを使用してクライアントを認証するを完了することを選択した場合にのみ必要です。 これらのシークレットをソース管理にコミットしないように、gitignore ファイルにこれを追加してください。
クロスプラットフォーム UI を実装する
main_page.dartで、ビルド 関数を次のように置き換えます。
@override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 40.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ FlatButton( child: Text("Register"), onPressed: registerButtonClicked, ), FlatButton( child: Text("Deregister"), onPressed: deregisterButtonClicked, ), ], ), ), ); }
必要なインポートを main_page.dart ファイルの先頭に追加します。
import 'package:push_demo/services/notification_registration_service.dart'; import 'config.dart';
_MainPageState クラスにフィールドを追加して、NotificationRegistrationServiceへの参照を格納します。
final notificationRegistrationService = NotificationRegistrationService(Config.backendServiceEndpoint, Config.apiKey);
_MainPageState クラスで、Register のイベント ハンドラーを実装し、onPressed イベントボタン 登録解除します。 対応する Register/登録解除 メソッドを呼び出し、結果を示すアラートを表示します。 void registerButtonClicked() async { try { await notificationRegistrationService.registerDevice(List<String>()); await showAlert(message: "Device registered"); } catch (e) { await showAlert(message: e); } } void deregisterButtonClicked() async { try { await notificationRegistrationService.deregisterDevice(); await showAlert(message: "Device deregistered"); } catch (e) { await showAlert(message: e); } } Future<void> showAlert({ message: String }) async { return showDialog<void>( context: context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( title: Text('PushDemo'), content: SingleChildScrollView( child: ListBody( children: <Widget>[ Text(message), ], ), ), actions: <Widget>[ FlatButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }
main.dart
、ファイルの先頭に次のインポートが存在することを確認します。 import 'package:flutter/material.dart'; import 'package:push_demo/models/push_demo_action.dart'; import 'package:push_demo/services/notification_action_service.dart'; import 'package:push_demo/main_page.dart';
NotificationActionService のインスタンスへの参照
格納して初期化する変数を宣言します。 final notificationActionService = NotificationActionService();
アクションがトリガーされたときにアラートの表示を処理する関数を追加します。
void notificationActionTriggered(PushDemoAction action) { showActionAlert(message: "${action.toString().split(".")[1]} action received"); } Future<void> showActionAlert({ message: String }) async { return showDialog<void>( context: navigatorKey.currentState.overlay.context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( title: Text('PushDemo'), content: SingleChildScrollView( child: ListBody( children: <Widget>[ Text(message), ], ), ), actions: <Widget>[ FlatButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }
main 関数を更新して、NotificationActionServiceactionTriggered ストリームを監視し、アプリの起動時にキャプチャされたすべてのアクションを確認します。
void main() async { runApp(MaterialApp(home: MainPage(), navigatorKey: navigatorKey,)); notificationActionService.actionTriggered.listen((event) { notificationActionTriggered(event as PushDemoAction); }); await notificationActionService.checkLaunchAction(); }
手記
これは、プッシュ通知アクションの受信と伝達を示すだけのものです。 通常、これらは、アラートを表示するのではなく、特定のビューに移動したり、データを更新したりするなど、警告なしで処理されます。
プッシュ通知用にネイティブ Android プロジェクトを構成する
Google Services JSON ファイルを追加する
コントロール android フォルダーの をクリックし、[Android Studioで開く] を選択します。 次に、Project ビューに切り替えます (まだ表示されていない場合)。 Firebase Consoleで PushDemo プロジェクトを設定したときに、先ほどダウンロードした google-services.json ファイルを見つけます。 次に、
アプリ モジュール ルート ディレクトリ (androidandroid アプリ ) にドラッグします。
ビルド設定とアクセス許可を構成する
プロジェクト ビューを Androidに切り替えます。 AndroidManifest.xml開き、INTERNET を追加し、アプリケーションの 要素の後に、終了 タグの前にアクセス許可を READ_PHONE_STATE します。
<manifest> <application>...</application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> </manifest>
Firebase SDK を追加する
Android Studio で、build.gradle ファイル (build.gradle (Project: android)Gradle スクリプト ) プロジェクト レベルを開きます。 buildscript
> の依存関係 ノードに 'com.google.gms:google-services' クラスパスがあることを確認します。buildscript { repositories { // Check that you have the following line (if not, add it): google() // Google's Maven repository } dependencies { // ... // Add the following line: classpath 'com.google.gms:google-services:4.3.3' // Google Services plugin } } allprojects { // ... repositories { // Check that you have the following line (if not, add it): google() // Google's Maven repository // ... } }
手記
Android Projectを作成するときに、Firebase コンソールの に記載されている手順に従って、最新バージョンを参照してください。
アプリ レベル build.gradle ファイル (Gradle Scripts>build.gradle (Module: app)) で、Google Services Gradle プラグイン適用します。 android ノードのすぐ上にプラグインを適用します。
// ... // Add the following line: apply plugin: 'com.google.gms.google-services' // Google Services plugin android { // ... }
同じファイルで、の依存関係 ノードに、Cloud Messaging Android ライブラリの依存関係を追加します。
dependencies { // ... implementation 'com.google.firebase:firebase-messaging:20.2.0' }
手記
Cloud Messaging Android クライアントのドキュメントに従って、最新バージョンを参照してください。
変更を保存し、[
今すぐ同期] ボタン (ツール バープロンプトから) をクリックするか、Gradle Filesを使用してプロジェクトを同期します。
Android のプッシュ通知を処理する
Android Studio で、com の [コントロール] パッケージ フォルダークリック をします。.pushdemo (メイン kotlin src アプリ) を your_organization [新しい ] メニューから [パッケージ ] を選択します。 サービス名前として を入力し、戻る押します。 コントロール をクリックし、[新しい] メニューから [Kotlin ファイル/クラス]サービス フォルダーの選択します。 名前 DeviceInstallationService を入力し、戻る押します。 次のコードを使用して、DeviceInstallationService を実装します。
package com.<your_organization>.pushdemo.services import android.annotation.SuppressLint import android.content.Context import android.provider.Settings.Secure import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @SuppressLint("HardwareIds") class DeviceInstallationService { companion object { const val DEVICE_INSTALLATION_CHANNEL = "com.<your_organization>.pushdemo/deviceinstallation" const val GET_DEVICE_ID = "getDeviceId" const val GET_DEVICE_TOKEN = "getDeviceToken" const val GET_DEVICE_PLATFORM = "getDevicePlatform" } private var context: Context private var deviceInstallationChannel : MethodChannel val playServicesAvailable get() = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS constructor(context: Context, flutterEngine: FlutterEngine) { this.context = context deviceInstallationChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, DEVICE_INSTALLATION_CHANNEL) deviceInstallationChannel.setMethodCallHandler { call, result -> handleDeviceInstallationCall(call, result) } } fun getDeviceId() : String = Secure.getString(context.applicationContext.contentResolver, Secure.ANDROID_ID) fun getDeviceToken() : String { if(!playServicesAvailable) { throw Exception(getPlayServicesError()) } // TODO: Revisit once we have created the PushNotificationsFirebaseMessagingService val token = "Placeholder_Get_Value_From_FirebaseMessagingService_Implementation" if (token.isNullOrBlank()) { throw Exception("Unable to resolve token for FCM.") } return token } fun getDevicePlatform() : String = "fcm" private fun handleDeviceInstallationCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { GET_DEVICE_ID -> { result.success(getDeviceId()) } GET_DEVICE_TOKEN -> { getDeviceToken(result) } GET_DEVICE_PLATFORM -> { result.success(getDevicePlatform()) } else -> { result.notImplemented() } } } private fun getDeviceToken(result: MethodChannel.Result) { try { val token = getDeviceToken() result.success(token) } catch (e: Exception) { result.error("ERROR", e.message, e) } } private fun getPlayServicesError(): String { val resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) if (resultCode != ConnectionResult.SUCCESS) { return if (GoogleApiAvailability.getInstance().isUserResolvableError(resultCode)){ GoogleApiAvailability.getInstance().getErrorString(resultCode) } else { "This device is not supported" } } return "An error occurred preventing the use of push notifications" } }
手記
このクラスは、
com.<your_organization>.pushdemo/deviceinstallation
チャネルに対応するプラットフォーム固有のメソッドを実装します。 これは、DeviceInstallationService.dart内のアプリの Flutter 部分で定義されました。 この場合、共通コードからネイティブ ホストへの呼び出しが行われます。 <your_organization> は、使用する場所を問わず、必ず独自の組織に置き換えてください。このクラスは、通知ハブ登録ペイロードの一部として一意の ID (Secure.AndroidIdを使用) を提供します。
別の
Kotlin File/Class を NotificationRegistrationServiceと呼ばれるサービス フォルダー追加し、次のコードを追加します。 package com.<your_organization>.pushdemo.services import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class NotificationRegistrationService { companion object { const val NOTIFICATION_REGISTRATION_CHANNEL = "com.<your_organization>.pushdemo/notificationregistration" const val REFRESH_REGISTRATION = "refreshRegistration" } private var notificationRegistrationChannel : MethodChannel constructor(flutterEngine: FlutterEngine) { notificationRegistrationChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, NotificationRegistrationService.NOTIFICATION_REGISTRATION_CHANNEL) } fun refreshRegistration() { notificationRegistrationChannel.invokeMethod(REFRESH_REGISTRATION, null) } }
手記
このクラスは、
com.<your_organization>.pushdemo/notificationregistration
チャネルに対応するプラットフォーム固有のメソッドを実装します。 これは、NotificationRegistrationService.dart内のアプリの Flutter 部分で定義されました。 この場合、ネイティブ ホストから共通コードへの呼び出しが行われます。 ここでも、<your_organization> を使用する場所を問わず、自分の組織に置き換えてください。別の
Kotlin File/Class を NotificationActionServiceというサービス フォルダー追加し、次のコードを追加します。 package com.<your_organization>.pushdemo.services import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel class NotificationActionService { companion object { const val NOTIFICATION_ACTION_CHANNEL = "com.<your_organization>.pushdemo/notificationaction" const val TRIGGER_ACTION = "triggerAction" const val GET_LAUNCH_ACTION = "getLaunchAction" } private var notificationActionChannel : MethodChannel var launchAction : String? = null constructor(flutterEngine: FlutterEngine) { notificationActionChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, NotificationActionService.NOTIFICATION_ACTION_CHANNEL) notificationActionChannel.setMethodCallHandler { call, result -> handleNotificationActionCall(call, result) } } fun triggerAction(action: String) { notificationActionChannel.invokeMethod(NotificationActionService.TRIGGER_ACTION, action) } private fun handleNotificationActionCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { NotificationActionService.GET_LAUNCH_ACTION -> { result.success(launchAction) } else -> { result.notImplemented() } } } }
手記
このクラスは、
com.<your_organization>.pushdemo/notificationaction
チャネルに対応するプラットフォーム固有のメソッドを実装します。 これは、NotificationActionService.dart内のアプリの Flutter 部分で定義されました。 この場合、呼び出しは双方向に行うことができます。 <your_organization> は、使用する場所を問わず、必ず独自の組織に置き換えてください。新しい
Kotlin File/Class をcom に追加します。 パッケージを pushNotificationsFirebaseMessagingServiceyour_organization .pushdemo 呼び出し、次のコードを使用して実装します。 package com.<your_organization>.pushdemo import android.os.Handler import android.os.Looper import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.<your_organization>.pushdemo.services.NotificationActionService import com.<your_organization>.pushdemo.services.NotificationRegistrationService class PushNotificationsFirebaseMessagingService : FirebaseMessagingService() { companion object { var token : String? = null var notificationRegistrationService : NotificationRegistrationService? = null var notificationActionService : NotificationActionService? = null } override fun onNewToken(token: String) { PushNotificationsFirebaseMessagingService.token = token notificationRegistrationService?.refreshRegistration() } override fun onMessageReceived(message: RemoteMessage) { message.data.let { Handler(Looper.getMainLooper()).post { notificationActionService?.triggerAction(it.getOrDefault("action", null)) } } } }
手記
このクラスは、アプリがフォアグラウンドで実行されているときに通知を処理します。 onMessageReceivedで受信した通知ペイロードにアクションが含まれている場合、
NotificationActionService でtriggerAction を条件付きで呼び出します。 これにより、Firebase トークンが onNewToken 関数をオーバーライドして再生成されるときに、NotificationRegistrationService で refreshRegistration も呼び出されます。もう一度、<your_organization> を使用する場所を問わず、自分の組織に置き換えてください。
AndroidManifest.xml (アプリ>src>main) で、
com.google.firebase.MESSAGING_EVENT
インテント フィルターを使用して、アプリケーションの 要素の下部に PushNotificationsFirebaseMessagingService を追加します。<manifest> <application> <!-- EXISTING MANIFEST CONTENT --> <service android:name="com.<your_organization>.pushdemo.PushNotificationsFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> </application> </manifest>
DeviceInstallationServiceに戻り、次のインポートがファイルの先頭に存在することを確認します。
package com.<your_organization>.pushdemo import com.<your_organization>.pushdemo.services.PushNotificationsFirebaseMessagingService
手記
<your_organization> を自分の組織の価値に置き換えます。
プレースホルダー テキスト Placeholder_Get_Value_From_FirebaseMessagingService_Implementation を更新して、PushNotificationFirebaseMessagingServiceからトークン値を取得します。
fun getDeviceToken() : String { if(!playServicesAvailable) { throw Exception(getPlayServicesError()) } // Get token from the PushNotificationsFirebaseMessagingService.token field. val token = PushNotificationsFirebaseMessagingService.token if (token.isNullOrBlank()) { throw Exception("Unable to resolve token for FCM.") } return token }
MainActivityで、次のインポートがファイルの先頭に存在することを確認します。
package com.<your_organization>.pushdemo import android.content.Intent import android.os.Bundle import com.google.android.gms.tasks.OnCompleteListener import com.google.firebase.iid.FirebaseInstanceId import com.<your_organization>.pushdemo.services.DeviceInstallationService import com.<your_organization>.pushdemo.services.NotificationActionService import com.<your_organization>.pushdemo.services.NotificationRegistrationService import io.flutter.embedding.android.FlutterActivity
手記
<your_organization> を自分の組織の価値に置き換えます。
DeviceInstallationServiceへの参照を格納する変数を追加します。
private lateinit var deviceInstallationService: DeviceInstallationService
processNotificationActions という関数を追加して、意図 にアクションという名前追加の値があるかどうかを確認します。 アプリの起動時にアクションが処理されている場合は、そのアクションを条件付きでトリガーするか、後で使用するために格納します。 private fun processNotificationActions(intent: Intent, launchAction: Boolean = false) { if (intent.hasExtra("action")) { var action = intent.getStringExtra("action"); if (action.isNotEmpty()) { if (launchAction) { PushNotificationsFirebaseMessagingService.notificationActionService?.launchAction = action } else { PushNotificationsFirebaseMessagingService.notificationActionService?.triggerAction(action) } } } }
processNotificationActionsを呼び出すには、onNewIntent 関数をオーバーライドします。
override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) processNotificationActions(intent) }
手記
MainActivity のLaunchMode は SingleTopに設定されているため、 インテント は、on を介して既存の 関数は、アクティビティ インスタンスに送信されます。 newIntentonCreate 関数ではなく、onCreate 関数であるため、onCreate 関数と onNewIntent 関数の両方で受信 インテント を処理する必要があります。onCreate 関数をオーバーライドし、deviceInstallationService を DeviceInstallationServiceの新しいインスタンス設定します。 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) flutterEngine?.let { deviceInstallationService = DeviceInstallationService(context, it) } }
pushNotificationFirebaseMessagingServicesで、
notificationActionService プロパティと notificationRegistrationService プロパティ設定します。 flutterEngine?.let { deviceInstallationService = DeviceInstallationService(context, it) PushNotificationsFirebaseMessagingService.notificationActionService = NotificationActionService(it) PushNotificationsFirebaseMessagingService.notificationRegistrationService = NotificationRegistrationService(it) }
同じ関数で、条件 FirebaseInstanceId.getInstance().instanceIdを呼び出します。
OnCompleteListener を実装して、refreshRegistrationを呼び出す前に、PushNotificationFirebaseMessagingService で結果のトークン 値設定します。 if(deviceInstallationService?.playServicesAvailable) { FirebaseInstanceId.getInstance().instanceId .addOnCompleteListener(OnCompleteListener { task -> if (!task.isSuccessful) return@OnCompleteListener PushNotificationsFirebaseMessagingService.token = task.result?.token PushNotificationsFirebaseMessagingService.notificationRegistrationService?.refreshRegistration() }) }
onCreateでは、関数の末尾 processNotificationActions を呼び出します。 launchAction 引数 true を使用して、アプリの起動時にこのアクションが処理されていることを示します。
processNotificationActions(this.intent, true)
手記
アプリを実行するたびに再登録し、デバッグ セッションから停止してプッシュ通知の受信を続ける必要があります。
プッシュ通知用にネイティブ iOS プロジェクトを構成する
ランナー ターゲットと Info.plist を構成する
Visual Studio Code で、[コントロール] ] をクリックし、[Xcodeで開く]ios フォルダーの [選択します。 Xcode
で、 ランナー (フォルダーではなく、上部にあるxcodeproj ) をクリックし、ランナー ターゲットを選択してから、[署名 & 機能]をします。 All ビルド構成が選択されたら、Teamの開発者アカウントを選択します。 [署名を自動的に管理する] オプションがオンになっていることを確認し、署名証明書とプロビジョニング プロファイルが自動的に選択されていることを確認します。 手記
新しいプロビジョニング プロファイルの値が表示されない場合は、Xcode
Preferences Account 選択して、署名 ID のプロファイルを更新してから、 [手動プロファイルのダウンロード] ボタンを選択してプロファイルをダウンロードしてみてください。+ 機能をクリックし、プッシュ通知を検索します。
プッシュ通知 の をダブルクリックして、この機能を追加します。Info.plist
開き、最小システム バージョン 13.0 に設定します。手記
このチュートリアルでは、iOS 13.0 以降の
実行されているデバイスのみがサポートされていますが、古いバージョンを実行しているデバイスをサポートするように拡張できます。 Runner.entitlements を開き、APS 環境の 設定が開発に設定されていることを確認します。
iOS のプッシュ通知を処理する
コントロール ] をクリックし (ランナー プロジェクト内)、名前として [Runner フォルダーの [Services を使用して新しいグループ を] を選択します。 コントロール の作成] をクリックし、[新しいファイルServices フォルダーの選択します。.. します。次に、Swiftファイル選択し、[次へ クリックします。 名前 DeviceInstallationService を指定し、[クリックします。 次 コードを使用して DeviceInstallationService.swift を実装します。
import Foundation class DeviceInstallationService { enum DeviceRegistrationError: Error { case notificationSupport(message: String) } var token : Data? = nil let DEVICE_INSTALLATION_CHANNEL = "com.<your_organization>.pushdemo/deviceinstallation" let GET_DEVICE_ID = "getDeviceId" let GET_DEVICE_TOKEN = "getDeviceToken" let GET_DEVICE_PLATFORM = "getDevicePlatform" private let deviceInstallationChannel : FlutterMethodChannel var notificationsSupported : Bool { get { if #available(iOS 13.0, *) { return true } else { return false } } } init(withBinaryMessenger binaryMessenger : FlutterBinaryMessenger) { deviceInstallationChannel = FlutterMethodChannel(name: DEVICE_INSTALLATION_CHANNEL, binaryMessenger: binaryMessenger) deviceInstallationChannel.setMethodCallHandler(handleDeviceInstallationCall) } func getDeviceId() -> String { return UIDevice.current.identifierForVendor!.description } func getDeviceToken() throws -> String { if(!notificationsSupported) { let notificationSupportError = getNotificationsSupportError() throw DeviceRegistrationError.notificationSupport(message: notificationSupportError) } if (token == nil) { throw DeviceRegistrationError.notificationSupport(message: "Unable to resolve token for APNS.") } return token!.reduce("", {$0 + String(format: "%02X", $1)}) } func getDevicePlatform() -> String { return "apns" } private func handleDeviceInstallationCall(call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case GET_DEVICE_ID: result(getDeviceId()) case GET_DEVICE_TOKEN: getDeviceToken(result: result) case GET_DEVICE_PLATFORM: result(getDevicePlatform()) default: result(FlutterMethodNotImplemented) } } private func getDeviceToken(result: @escaping FlutterResult) { do { let token = try getDeviceToken() result(token) } catch let error { result(FlutterError(code: "UNAVAILABLE", message: error.localizedDescription, details: nil)) } } private func getNotificationsSupportError() -> String { if (!notificationsSupported) { return "This app only supports notifications on iOS 13.0 and above. You are running \(UIDevice.current.systemVersion)" } return "An error occurred preventing the use of push notifications." } }
手記
このクラスは、
com.<your_organization>.pushdemo/deviceinstallation
チャネルに対応するプラットフォーム固有のメソッドを実装します。 これは、DeviceInstallationService.dart内のアプリの Flutter 部分で定義されました。 この場合、共通コードからネイティブ ホストへの呼び出しが行われます。 <your_organization> は、使用する場所を問わず、必ず独自の組織に置き換えてください。このクラスは、通知ハブ登録ペイロードの一部として一意の ID (UIDevice.identifierForVendor 値を使用) を提供します。
NotificationRegistrationServiceという名前の Services フォルダーに別の Swift ファイル を追加し、次のコードを追加します。
import Foundation class NotificationRegistrationService { let NOTIFICATION_REGISTRATION_CHANNEL = "com.<your_organization>.pushdemo/notificationregistration" let REFRESH_REGISTRATION = "refreshRegistration" private let notificationRegistrationChannel : FlutterMethodChannel init(withBinaryMessenger binaryMessenger : FlutterBinaryMessenger) { notificationRegistrationChannel = FlutterMethodChannel(name: NOTIFICATION_REGISTRATION_CHANNEL, binaryMessenger: binaryMessenger) } func refreshRegistration() { notificationRegistrationChannel.invokeMethod(REFRESH_REGISTRATION, arguments: nil) } }
手記
このクラスは、
com.<your_organization>.pushdemo/notificationregistration
チャネルに対応するプラットフォーム固有のメソッドを実装します。 これは、NotificationRegistrationService.dart内のアプリの Flutter 部分で定義されました。 この場合、ネイティブ ホストから共通コードへの呼び出しが行われます。 ここでも、<your_organization> を使用する場所を問わず、自分の組織に置き換えてください。別の
Swift ファイル を NotificationActionServiceという名前のServices フォルダー追加し、次のコードを追加します。 import Foundation class NotificationActionService { let NOTIFICATION_ACTION_CHANNEL = "com.<your_organization>.pushdemo/notificationaction" let TRIGGER_ACTION = "triggerAction" let GET_LAUNCH_ACTION = "getLaunchAction" private let notificationActionChannel: FlutterMethodChannel var launchAction: String? = nil init(withBinaryMessenger binaryMessenger: FlutterBinaryMessenger) { notificationActionChannel = FlutterMethodChannel(name: NOTIFICATION_ACTION_CHANNEL, binaryMessenger: binaryMessenger) notificationActionChannel.setMethodCallHandler(handleNotificationActionCall) } func triggerAction(action: String) { notificationActionChannel.invokeMethod(TRIGGER_ACTION, arguments: action) } private func handleNotificationActionCall(call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case GET_LAUNCH_ACTION: result(launchAction) default: result(FlutterMethodNotImplemented) } } }
手記
このクラスは、
com.<your_organization>.pushdemo/notificationaction
チャネルに対応するプラットフォーム固有のメソッドを実装します。 これは、NotificationActionService.dart内のアプリの Flutter 部分で定義されました。 この場合、呼び出しは双方向に行うことができます。 <your_organization> は、使用する場所を問わず、必ず独自の組織に置き換えてください。AppDelegate.swiftで、前に作成したサービスへの参照を格納する変数を追加します。
var deviceInstallationService : DeviceInstallationService? var notificationRegistrationService : NotificationRegistrationService? var notificationActionService : NotificationActionService?
通知データを処理 processNotificationActions という関数を追加します。 アプリの起動時にアクションが処理されている場合は、そのアクションを条件付きでトリガーするか、後で使用するために格納します。
func processNotificationActions(userInfo: [AnyHashable : Any], launchAction: Bool = false) { if let action = userInfo["action"] as? String { if (launchAction) { notificationActionService?.launchAction = action } else { notificationActionService?.triggerAction(action: action) } } }
DeviceInstallationServiceの トークン 値を設定する didRegisterForRemoteNotificationsWithDeviceToken 関数をオーバーライドします。 次に、NotificationRegistrationServicerefreshRegistration を呼び出します。
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { deviceInstallationService?.token = deviceToken notificationRegistrationService?.refreshRegistration() }
userInfo 引数を processNotificationActions 関数に渡す didReceiveRemoteNotification 関数をオーバーライドします。
override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { processNotificationActions(userInfo: userInfo) }
didFailToRegisterForRemoteNotificationsWithError 関数
をオーバーライドして、エラーをログに記録します。 override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print(error); }
手記
これは非常にプレースホルダーです。 運用環境のシナリオでは、適切なログ記録とエラー処理を実装する必要があります。
didFinishLaunchingWithOptions で、deviceInstallationService 、notificationRegistrationService 、および notificationActionService 変数をインスタンス化します。 let controller : FlutterViewController = window?.rootViewController as! FlutterViewController deviceInstallationService = DeviceInstallationService(withBinaryMessenger: controller.binaryMessenger) notificationRegistrationService = NotificationRegistrationService(withBinaryMessenger: controller.binaryMessenger) notificationActionService = NotificationActionService(withBinaryMessenger: controller.binaryMessenger)
同じ関数で、条件付きで承認を要求し、リモート通知に登録します。
if #available(iOS 13.0, *) { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in if (granted) { DispatchQueue.main.async { let pushSettings = UIUserNotificationSettings(types: [.alert, .sound, .badge], categories: nil) application.registerUserNotificationSettings(pushSettings) application.registerForRemoteNotifications() } } } }
launchOptions に remoteNotification キーが含まれている場合は、didFinishLaunchingWithOptions 関数の末尾に processNotificationActions を呼び出します。 結果の userInfo オブジェクトを渡し、launchAction 引数 true を使用します。 true 値は、アプリの起動時にアクションが処理されていることを示します。
if let userInfo = launchOptions?[.remoteNotification] as? [AnyHashable : Any] { processNotificationActions(userInfo: userInfo, launchAction: true) }
ソリューションをテストする
バックエンド サービスを介した通知の送信をテストできるようになりました。
テスト通知を送信する
Postmanで新しいタブ
開きます。 要求を POST
に設定し、次のアドレスを入力します。 https://<app_name>.azurewebsites.net/api/notifications/requests
API キー セクションを使用してクライアントを認証する
を完了する場合は、 apikey 値を含めるように要求ヘッダーを構成してください。鍵 価値 apikey <your_api_key> Body のraw オプションを選択し、書式設定オプションの一覧から JSON選択し、JSON コンテンツ プレースホルダーを含めます。 { "text": "Message from Postman!", "action": "action_a" }
ウィンドウの右上にある [ の保存] ボタンの下にある [Code] ボタンを選択します。 要求は、HTML に対して表示される場合の次の例のようになります (apikey ヘッダーを含めたかどうかによって異なります)。
POST /api/notifications/requests HTTP/1.1 Host: https://<app_name>.azurewebsites.net apikey: <your_api_key> Content-Type: application/json { "text": "Message from backend service", "action": "action_a" }
PushDemo アプリケーションを、ターゲット プラットフォームの一方または両方 (Android と iOS) で実行します。
手記
Android でテスト、デバッグで実行されていないことを確認するか、アプリケーションを実行してアプリがデプロイされている場合は、アプリを強制的に閉じて起動ツールから再度起動します。
PushDemo アプリで、[Register] ボタンをタップします。
Postman
戻り、 [コード スニペットの生成] ウィンドウを閉じて (まだ行っていない場合)、[ の送信] ボタンをクリックします。Postman で
200 OK 応答受け取り、アラートがアプリに表示され、受け取った ActionA アクション 表示されていることを確認します。 PushDemo アプリを閉じ、Postmanでもう一度 [送信] ボタンクリックします。 Postman で
200 OK 応答返されることを検証します。 正しいメッセージを含む PushDemo アプリの通知領域に通知が表示されることを確認します。 通知をタップしてアプリを開き、アラート 受け取った
ActionA アクションを表示したことを確認します。 Postman
戻り、前の要求本文を変更して、 アクションの 値にaction_a するのではなく、action_b を指定するサイレント通知を送信します。{ "action": "action_b", "silent": true }
アプリを開いたまま、Postmanの [
送信] ボタンクリックします。 Postman で
200 OK 応答を受け取り、受け取った ActionA アクションではなく、 を受け取った ActionB アクション 示すアラート アプリに表示されることを検証します。 PushDemo アプリを閉じ、Postmanでもう一度 [送信] ボタンクリックします。 Postman で
200 OK 応答受け取り、通知領域にサイレント通知が表示されないかどうかを検証します。
トラブルシューティング
バックエンド サービスからの応答なし
ローカルでテストする場合は、バックエンド サービスが実行されており、正しいポートを使用していることを確認します。
Azure API Appに対してテストする場合は、サービスが実行中であり、デプロイされていて、エラーなしで開始されていることを確認します。
クライアント経由でテストする場合は、Postman またはモバイル アプリ構成 https://<api_name>.azurewebsites.net/
または https://localhost:5001/
する必要があります。
デバッグ セッションの開始または停止後に Android で通知を受信しない
デバッグ セッションを開始または停止した後に、もう一度登録してください。 デバッガーによって、新しい Firebase トークンが生成されます。 通知ハブのインストールも更新する必要があります。
バックエンド サービスからの 401 状態コードの受信
apikey 要求ヘッダーを設定していることを確認します。この値は、バックエンド サービス用に構成したものと一致します。
ローカルでテストするときにこのエラーが発生した場合は、クライアント構成で定義したキー値が、APIで使用される Authentication:ApiKey ユーザー設定値と一致していることを確認します。
API アプリを使用してテストする場合は、クライアント構成ファイルのキー値が、API アプリで使用している Authentication:ApiKey アプリケーション設定と一致していることを確認します。
手記
バックエンド サービスをデプロイした後にこの設定を作成または変更した場合は、サービスを有効にするためにサービスを再起動する必要があります。
API キー セクションを使用してクライアントの認証
バックエンド サービスからの 404 状態コードの受信
エンドポイントと HTTP 要求メソッドが正しいことを検証します。 たとえば、エンドポイントは次のように示す必要があります。
- [PUT]
を する -
[DELETE]
https://<api_name>.azurewebsites.net/api/notifications/installations/<installation_id>
-
[POST]
https://<api_name>.azurewebsites.net/api/notifications/requests
または、ローカルでテストする場合:
- [PUT]
を する -
[DELETE]
https://localhost:5001/api/notifications/installations/<installation_id>
-
[POST]
https://localhost:5001/api/notifications/requests
クライアント アプリでベース アドレスを指定するときは、/
で終わることを確認します。 ローカルでテストする場合は、ベース アドレスを https://<api_name>.azurewebsites.net/
または https://localhost:5001/
する必要があります。
登録できず、通知ハブのエラー メッセージが表示される
テスト デバイスにネットワーク接続があることを確認します。 次に、HttpResponseの StatusCode プロパティ値を検査するブレークポイントを設定して、Http 応答状態コードを決定します。
状態コードに基づいて、該当する場合は、前のトラブルシューティングの推奨事項を確認します。
それぞれの API に対してこれらの特定の状態コードを返す行にブレークポイントを設定します。 次に、ローカルでデバッグするときにバックエンド サービスを呼び出してみてください。
適切なペイロードを使用 Postman を使用して、バックエンド サービスが期待どおりに動作していることを確認します。 対象のプラットフォームのクライアント コードによって作成された実際のペイロードを使用します。
プラットフォーム固有の構成セクションを確認して、手順が実行されていないことを確認します。 適切なプラットフォームの installation id
変数と token
変数に対して適切な値が解決されていることを確認します。
デバイスの ID を解決できないエラー メッセージが表示される
プラットフォーム固有の構成セクションを確認して、手順が実行されていないことを確認します。
関連リンク
- Azure Notification Hubs の概要
- macOS への Flutter のインストールの
- Windows への Flutter のインストールの
- バックエンド操作 用の
Notification Hubs SDK - GitHub で Notification Hubs SDK を
する - アプリケーション バックエンド に登録する
- 登録管理
- タグの操作
- カスタム テンプレートの操作
次の手順
これで、基本的な Flutter アプリがバックエンド サービス経由で通知ハブに接続され、通知を送受信できるようになります。
このチュートリアルで使用する例を独自のシナリオに合わせて調整する必要がある可能性があります。 より堅牢なエラー処理、再試行ロジック、ログ記録の実装も推奨されます。
Visual Studio App Center は、トラブルシューティングに役立つ 分析 と 診断 を提供するモバイル アプリにすばやく組み込むことができます。