Azure Notification Hubsを使ってXamarin.Android アプリにプッシュ通知を試す

# はじめに
最近AZ-204を受験して合格しました。
普段仕事で使わない機能についてかなり問われたのですが、
そのなかでAzure Notification Hubsをしっかり使ってみたいなと思ったので試してみました。
Xamarin.Androidも今回初めて使用。

# 環境とか必要なもの
- Visual Studio 2019
- Azureのアカウント
- Googleのアカウント

私の携帯がAndroidということと、macを持っていなかったので今回Androidを採用しました。

# 作ってみたもの
Xamarin.Android側のアプリの発行はしていないので、デバックでのみの確認ですが。。。
Azure Functionsのタイマートリガーを使って、毎週火曜日の7:25に
もうすぐモルカーが始まるプッシュ通知を送信。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201826/f2a53c1b-9c2b-3711-8187-f40ee1eac48f.png)

# つくりかた
[公式のチュートリアル](https://docs.microsoft.com/ja-jp/azure/notification-hubs/xamarin-notification-hubs-push-notifications-android-gcm)を基本的に参考にして作ったのですが、うまくいかないところがあり、
こちらのサイト[Azure Notification HubsでXamarin.FormsアプリにPush通知 メモ (Android編)](https://shuhelohelo.hatenablog.com/entry/2020/05/23/230302)を真似て作ったらうまくいきました。

## Firebase プロジェクトを作成
Googleのアカウントを持っていれば無料で作れました。
作成方法は[MSの公式チュートリアル](https://docs.microsoft.com/ja-jp/azure/notification-hubs/xamarin-notification-hubs-push-notifications-android-gcm#create-a-firebase-project-and-enable-firebase-cloud-messaging)通りです。

## Azureポータルから通知ハブの作成
こちらも[MSの公式チュートリアル](https://docs.microsoft.com/ja-jp/azure/notification-hubs/xamarin-notification-hubs-push-notifications-android-gcm#create-a-notification-hub)通りに作成しました。

## Xamarin.Android アプリを作成し、通知ハブに接続
Visual Studio プロジェクトを作成し、NuGet パッケージを追加する、Google Services JSON ファイルを追加するという部分までは、[MSの公式チュートリアル](https://docs.microsoft.com/ja-jp/azure/notification-hubs/xamarin-notification-hubs-push-notifications-android-gcm#create-a-xamarinandroid-app-and-connect-it-to-notification-hub)通りに作成しました。

その先のソースコードは[Azure Notification HubsでXamarin.FormsアプリにPush通知 メモ (Android編)](https://shuhelohelo.hatenablog.com/entry/2020/05/23/230302)を参考にして作成しています。

### Firebase Cloud Messaging の登録

```AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="Google Firebase ConsoleでプロジェクトにFirebase Cloud Messagingを追加するときに入力したパッケージ名">
	<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
	<uses-permission android:name="android.permission.WAKE_LOCK" />
	<uses-permission android:name="android.permission.GET_ACCOUNTS" />
	<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"></application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
```

### 定数値を定義
Constants.csクラスを作成し、定数値を定義します。

```Constants.cs
public static class Constants
    {
        public const string ListenConnectionString = "Azureで作成したNotification Hub内のAccess Policiesの名前(DefaultListenSharedAccessSignature)";
        public const string NotificationHubName = "Azureで作成したNotification Hubの名前";
    }
```

### MainActivity.csの編集

```MainActivity.cs
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        public const string TAG = "MainActivity";
        internal static readonly string CHANNEL_ID = "my_notification_channel";

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            if (Intent.Extras != null)
            {
                foreach (var key in Intent.Extras.KeySet())
                {
                    if (key != null)
                    {
                        var value = Intent.Extras.GetString(key);
                        Log.Debug(TAG, "Key: {0} Value: {1}", key, value);
                    }
                }
            }

            IsPlayServicesAvailable();
            CreateNotificationChannel();
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
        public bool IsPlayServicesAvailable()
        {
            int resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this);
            if (resultCode != ConnectionResult.Success)
            {
                if (GoogleApiAvailability.Instance.IsUserResolvableError(resultCode))
                    Log.Debug(TAG, GoogleApiAvailability.Instance.GetErrorString(resultCode));
                else
                {
                    Log.Debug(TAG, "This device is not supported");
                    Finish();
                }
                return false;
            }

            Log.Debug(TAG, "Google Play Services is available.");
            return true;
        }
        private void CreateNotificationChannel()
        {
            if (Build.VERSION.SdkInt < BuildVersionCodes.O)
            {
                // Notification channels are new in API 26 (and not a part of the
                // support library). There is no need to create a notification
                // channel on older versions of Android.
                return;
            }

            var channelName = CHANNEL_ID;
            var channelDescription = string.Empty;
            var channel = new NotificationChannel(CHANNEL_ID, channelName, NotificationImportance.Default)
            {
                Description = channelDescription
            };

            var notificationManager = (NotificationManager)GetSystemService(NotificationService);
            notificationManager.CreateNotificationChannel(channel);
        }
    }

```

### MyFirebaseMessagingService.csの追加
ここでは受信したメッセージの処理をしています。
SetContentTitleのところを変えてあげれば、push通知時のタイトルが変えられますね。

```MyFirebaseMessagingService.cs
    [Service]
    [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
    [IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
    public class MyFirebaseMessagingService : FirebaseMessagingService
    {
        const string TAG = "MyFirebaseMsgService";
        NotificationHub hub;

        public override void OnMessageReceived(RemoteMessage message)
        {
            Log.Debug(TAG, "From: " + message.From);
            if (message.GetNotification() != null)
            {
                //These is how most messages will be received
                Log.Debug(TAG, "Notification Message Body: " + message.GetNotification().Body);
                SendNotification(message.GetNotification().Body);
            }
            else
            {
                //Only used for debugging payloads sent from the Azure portal
                SendNotification(message.Data.Values.First());

            }
        }

        void SendNotification(string messageBody)
        {
            var intent = new Intent(this, typeof(MainActivity));
            intent.AddFlags(ActivityFlags.ClearTop);
            var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);

            var notificationBuilder = new NotificationCompat.Builder(this, MainActivity.CHANNEL_ID);

            notificationBuilder.SetContentTitle("FCM Message")
                        .SetSmallIcon(Resource.Drawable.ic_launcher)
                        .SetContentText(messageBody)
                        .SetAutoCancel(true)
                        .SetShowWhen(false)
                        .SetContentIntent(pendingIntent);

            var notificationManager = NotificationManager.FromContext(this);

            notificationManager.Notify(0, notificationBuilder.Build());
        }

        public override void OnNewToken(string token)
        {
            Log.Debug(TAG, "FCM token: " + token);
            SendRegistrationToServer(token);
        }

        void SendRegistrationToServer(string token)
        {
            // Register with Notification Hubs
            hub = new NotificationHub(Constants.NotificationHubName,
                                        Constants.ListenConnectionString, this);

            var tags = new List<string>() { };
            var regID = hub.Register(token, tags.ToArray()).RegistrationId;

            Log.Debug(TAG, $"Successful registration of ID {regID}");
        }
    }

```

## Azure Functionsのタイマートリガー作成
私はVisual Studioから作成したものをAzureで発行しました。
[こちらの方](http://blog.hiros-dot.net/?p=10189)の作成記事がわかりやすくていいかもです。

```TestFunction.cs
        [FunctionName("TestFunction")]
        public static async Task Run([TimerTrigger("0 25 7 * * 2")]TimerInfo myTimer, ILogger log)
        {
            NotificationHubClient _hub = NotificationHubClient.CreateClientFromConnectionString("Azureで作成したNotification Hub内のAccess Policiesの名前(DefaultListenSharedAccessSignature)", "Azureで作成したNotification Hubの名前");

            string message = "もうすぐモルカーの時間です。";
            var androidMessage = "{\"data\":{\"message\": \"" + message + "\"}}";
            await _hub.SendFcmNativeNotificationAsync(androidMessage);
        }
```

本当はhubの情報は違うクラスに持っといたほうがいいですが、今回お試しだったので直書きしてます。

# 試してみる
AzureFunctionsからテストで動かしているので時間がおかしいですが。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201826/35846dc2-7bca-5c4d-9254-ca7f81bb58d3.png)

すごく寂しい感じなので、画像とかも載せたいですね。


# さいごに
Xamarinをよくわかってないのもあってか、公式のチュートリアル通りにやってもうまくいかず、？？？となったのですが、最初に紹介したサイトのおかげでうまく通知させることまでひとまずできました。
AZ-204とりあえず取ったはいいのですが、このテストで出てきた他の機能ももう少し見てみたいところです。

# 参考URL
- [公式のチュートリアル](https://docs.microsoft.com/ja-jp/azure/notification-hubs/xamarin-notification-hubs-push-notifications-android-gcm)
- [Azure Notification HubsでXamarin.FormsアプリにPush通知 メモ (Android編)](https://shuhelohelo.hatenablog.com/entry/2020/05/23/230302)
- [タイマートリガーで動作する関数を作成するには？](http://blog.hiros-dot.net/?p=10189)