ぴよぴよエンジニアの日記

クラウドベンダーに勤める見習いSEの日記です。発言は私自身の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

部門旅行用のアプリを Xamarin.Forms で作ってリリースしました

9月29日、30日と部門の研修旅行がありまして、研修旅行用のアプリをリリースしたお話です.

Xamarin.Forms で実装していて、iOS, Android 両対応です.

ダウンロード数は、Android 20 対して、iOS が 70 という割合でした.
想像より iOS の割合が多いなという印象です.


CAMS2017

CAMS2017

  • Daiki Kawanuma
  • Navigation
  • Free
play.google.com

*ランタイム課金の関係上、今はサーバーサイド止めています.



期間

期間にして2ヶ月費やしてますが、実際の作業期間だと1ヶ月弱といった感じです.

f:id:Santea:20170930180131p:plain:w250

GitHub の草だとこんな感じ.
一番濃いところがリリース直前です.

全体の作業時間は正確に把握していませんが、体感で60~70時間くらいかなと思います.



クライアントサイド

f:id:Santea:20170930192810j:plain

クライアントサイドは Xamarin.Forms + Prism.Forms + ReactiveProperty です.

実装開始時は MVVM でちゃんと作ろうと考えていましたが、結果を見ると残念なものになってしまいましたw

Prism はほぼ INavigationService にしか使っていない有様で、ReactiveProperty の使い方も fat ViewModel な実装になっています(というか、ほぼ Model 書いてない汗).


ただ、Xamarin.Forms で実装した成果はちゃんと挙がっていて、短期間で iOS, Android の2つを仕上げるには View も共通化できる Xamarin.Forms を使うしか選択肢が無かったように思えます(もちろん Xamarin.Forms が唯一解という意味ではなく、View まで共通化できるクロスプラットフォームが良かったという話です).

凝った機能が無いなら Web アプリでもいいじゃんという意見もあるかと思いますが、URL を配布してブックマークさせるという工程は意外にハードルが高いと思っていて、その点アプリなら1回ダウンロードしてもらえばすぐに起動してもらえるので、ガワだけでもアプリ化するのは有効だと思います.



サーバーサイド

サーバーサイドは SpringBoot + Doma2 + Tymeleaf で、Bluemix 上の Liberty for Java に乗せています.

同期のスキルセットを考えて、サーバーサイドは Java にするかくらいの気持ちで決めました.わりとテキトーです.

今思うと、LoopBack などでもっと簡易に実装できたなぁと思うので、サーバーサイドは一度見直したいところです.



画面

f:id:Santea:20170930204832j:plain

画面数は root を含めると10画面です(上記+ログイン画面、WebView).

CardView の様な View などは Evolve 公式アプリを参考にしています.
Evolve のアプリは学ぶところが色々あって、非常に参考になりました.
(CustomRender でなく、Forms の View でやりくりしてるところなど凄いです)



まとめ

今回は Xamarin.Forms を、あくまで内向けですが実戦投入してみました.
短期間の軽量な開発だと、やはりクロスプラットフォームは光るところがあるなと感じました.

今回の開発で得たいくつかの気づきは、今後小出しにしていきたいと思います.


以上です.

Bluemix の Push Notifications を試してみた

最近はプッシュ通知といえば mBaaS を使うのがスタンダードという風潮を感じます.

Amazon SNSAzure Notification Hubs など、各社プッシュ通知のサービスを提供しています.


Bluemix にもプッシュ通知のサービスが存在します.

console.bluemix.net


価格プランは"ライト"と"基本"の2つになります.

f:id:Santea:20170801213024p:plain

f:id:Santea:20170801213252p:plain


クライアント SDK は以下の4つが用意されています.

サーバー SDK は以下の3つが用意されています.

  • Java
  • Node.js
  • Swift


今回はクライアントサイドに Android SDK を、サーバーサイドに Java SDK を用いたパターンを試してみたいと思います.



クライアントサイド

github.com
github.com

上記が iOS, Android 用の SDK となります.
今回は Android の実装を行います.


github.com

サンプルアプリはこちらになります.
サンプルアプリの中でポイントとなるところを抜粋して説明します.


AndroidManifest.xml

<activity
    android:name=".MainActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <!--Notification Intent -->
    <intent-filter>
        <action android:name="com.ibm.hellopush.IBMPushNotification" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Activity に通知インテントを受けるための記述を加えます.

<service android:exported="true" android:name="com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPushIntentService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
<service android:exported="true" android:name="com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPush">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>

IntentService の記述を加えます.
自前で IntentService を書かなくてもよいのは便利ですね.


MainActivity.java

MFPPushResponseListener registrationResponselistener = new MFPPushResponseListener<String>() {
            @Override
            public void onSuccess(String response) {
                // Split response and convert to JSON object to display User ID confirmation from the backend
                String[] resp = response.split("Text: ");
                try {
                    JSONObject responseJSON = new JSONObject(resp[1]);
                    setStatus("Device Registered Successfully with USER ID " + responseJSON.getString("userId"), true);
                } catch (JSONException e) {
                    e.printStackTrace();
                }

                Log.i(TAG, "Successfully registered for push notifications, " + response);
                // Start listening to notification listener now that registration has succeeded
                push.listen(notificationListener);
            }
            ...
};

トークン登録時のリスナーの記述です.
レスポンスの JSON は以下のようなものが返ってきます.

{"createdTime":"2017-07-27T13:45:33Z",
"lastUpdatedTime":"2017-07-31T09:18:04Z",
"createdMode":"API",
"deviceId":"XXXXXXX-01b0-36df-96d7-3cbc3c32118f",
"userId":"1234567890",
"token":"XXXXXXXXX-VrbN5G5QHqw9snkRhzNEf6-XXXXXX",
"platform":"G",
"href":"https:\/\/imfpush.ng.bluemix.net\/imfpush\/v1\/apps\/XXXXXX-43c2-8bfc-c5c2eb0e9124\/devices\/XXXXXX-36df-96d7-3cbc3c32118f"}

こちらに含まれる deviceId をサーバーサイドで指定することで、トークンを意識することなく通知を送れます.
この deviceId はアプリ再インストールなどでトークンが変わっても維持されるものです.
トークンのメンテナンスが必要ないのは大変便利ですね.

push.registerDeviceWithUserId("1234567890",registrationResponselistener);

先ほどの JSON に含まれていた userId はトークン登録時に明示的に指定することができます.
これにより、複数のデバイスを1人のユーザーに紐づけることが可能なり、ユーザー単位でのプッシュ通知送信を実現できます.


クライアントサイドの説明は以上になります.



サーバーサイド

今回は maven プロジェクトで実装しました.


pox.xml

<dependencies>
	<dependency>
		<groupId>com.ibm.mobilefirstplatform.serversdk.java</groupId>
		<artifactId>push</artifactId>
		<version>1.0.1</version>
	</dependency>
</dependencies>

上記の dependency の記述を追加します.



public static void main( String[] args ) throws SSLException, IOException
    {
        //(appGuid, appSecret)
        PushNotifications.init("XXXXXXXX-5ce7-43c2-8bfc", "XXXXXXXX-89d0-40b1-bdaf", PushNotifications.US_SOUTH_REGION);

        NotificationBuilder builder = new NotificationBuilder("This is the notification's text!");

        JSONObject notification = builder
    	.setTarget(new String[]{"XXXXXXX-01b0-36df-96d7-3cbc3c32118f"},
    			new String[]{"1234567890"},
    			new PushNotificationsPlatform[]{PushNotificationsPlatform.GOOGLE},
    			new String[]{"Tag1", "Tag2"})
    	.build();


    	PushNotifications.send(notification, new PushNotificationsResponseListener(){

    		public void onSuccess(int statusCode, String responseBody) {
    			System.out.println("Successfully sent push notification! Status code: " + statusCode + " Response body: " + responseBody);

    		}

    		public void onFailure(Integer statusCode, String responseBody, Throwable t) {
    			System.out.println("Failed sent push notification. Status code: " + statusCode + " Response body: " + responseBody);

    			if(t != null){
    				t.printStackTrace();
    			}
    		}
    	});
}

こちらがサーバーサイドのコードになります.

//(appGuid, appSecret)
PushNotifications.init("XXXXXXXX-5ce7-43c2-8bfc", "XXXXXXXX-89d0-40b1-bdaf", PushNotifications.US_SOUTH_REGION);

初期化の部分では、第一引数にサービス資格情報の appGuid を、第二引数に appSecret を指定します.

JSONObject notification = builder
    	.setTarget(new String[]{"XXXXXXX-01b0-36df-96d7-3cbc3c32118f"}, // deviceId
    			new String[]{"1234567890"}, // userId
    			new PushNotificationsPlatform[]{PushNotificationsPlatform.GOOGLE}, // Platform
    			new String[]{"Tag1", "Tag2"}) // Tag
    	.build();

上記がプッシュ通知オブジェクト生成部分です.
deviceId やクライアントサイドで登録した userId を指定することができます.
いづれか1つ以上を指定する必要があります(指定しない場合は Null または空文字をセットします).

builder.setMessageURL(urlToBeIncludedWithThePushNotification)
	.setTarget(deviceIdArray, userIdArray, platformArray, tagNameArray)
	.setAPNSSettings(badge, category, iosActionKey, payload, soundFile, APNSNotificationType)
	.setGCMSettings(collapseKey, delayWhileIdle, jsonPayload, GCMPriority.HIGH, soundFile, secondsToLive)
        .build();

また、各プラットフォームごとの設定も個別に行えるようになっています.


以上がサーバーサイドの実装でした.



まとめ

今回は Bluemix の Push Notifications を試してみました.


mBaaS を利用することで、プラットフォーム間の差異を吸収できたり、トークンを意識する必要がなかったりと、自前で実装するのに比べ優位な点が見えました.
気になるランニングコストも100万リクセストにつき1$であれば運用しやすいかと思います.


Bluemix Push Notifications ならではの特徴としては、やはり サーバーサイド Swift の SDK が提供されているところでしょうか.
サーバーサイドを Swift で書かれている方には有効な選択肢になるかと思います.


以上です.