この記事は今流行りのAdventCalendarとは、関係ありません。
諸事情あり、このタイミングでの公開となりました。
4月よりアライドアーキテクツ (以下、弊社) で新卒エンジニアとして従事しております、ゆーたろーと申します:) 大学時代はプログラムとはほぼ無縁の研究をしていました。ネズミの解剖をしていました。ディズニーシーが好きです。
前提
- 弊社2015年度新卒開発研修は3人1チームで行うAndroidアプリ開発
(詳細は他の新卒の記事に上がっているかもしれません。) - 我々Bチームはメッセンジャーアプリに挑戦
- サーバーサイドで便利なParse.comを紹介してもらい導入
- ネイティブアプリなんだしPush通知は使っときたいよね (結果、サーバーサイドも割りかし実装するはめに)
- Parse.com Android_SDK_1.9.2を使用
- 研修アプリに準拠しているため、スタンダードと齟齬のある可能性大いにあり
ということで、今回はメッセンジャーアプリでよくある
①友達からのメッセージがPush通知でお知らせされる
②Pushから受け取ったデータを自動的にviewに追加する
という2点について実装したいと思います。
今回やってみること
やることはざっくり以下4点
- Parse.comからPush通知を受け取るための準備
- ParseCloudCodeでPushを投げる
- ParsePushBroadcastReceiverを継承したReceiverの実装
- 表示用のActivityとListViewAdapterの実装
準備
①Parse.comからテスト用Push通知を受け取れるようにする
まずはParse.comへのアプリの登録からPush通知を受け取れるところまでの準備をしてください。基本的に公式のTutorialに則って行けると思います。
これでParse.comからPush通知を受け取れるようになったはず。
②ParseCloudCodeの準備
Parse.comを使うのなら、サーバーサイドの実装なんて考えなくてもいい気もしますが、そうも言ってられないのでCloudCodeを実装するための準備をします。これも公式QuickStart参照
Mac/Linux or Window
CloudCodeの実装はJavaScriptです。 (自分は初めて書きました。)
③Messageテーブルを作成
Parse.comにはDBも付属していますので、これを使います。Parse.comではテーブルのことをクラスと呼んでいますが、わかりづらいのでテーブルで進めます。ユーザーの登録等に関しては割愛します。
今回はMessageテーブルを作り、「username」と「body」のカラムをString型で追加しました。
Pushを投げるParseColudCodeの実装
やっと本題な気がします。まずはMessageテーブルに保存された情報を元に、AndroidにPush通知を投げるためのサーバー側の実装です。トリガFunctionのafterSave()を使いました。この関数はオブジェクトがMessageテーブルに保存される度に呼び出されます。
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
Parse.Cloud.afterSave("Message", function(request){ Parse.Cloud.useMasterKey(); //特権ユーザーにチェンジ //保存されたオブジェクトを取り出す var saved = request.object; var username = saved.get("username"); var body = saved.get("body"); //Push通知で送信するメッセージ var message = "[" + username + "さん]" + body; //Push通知を送る var pushQuery = new Parse.Query(Parse.Installation); Parse.Push.send({ where: pushQuery, data: { alert: message, sendMessage: { username: username, body: body } } }, { success: function(){ console.log("PushSuccess"); //ダッシュボードのLogsに出ます }, error:function(){ console.log("PushError"); } } ); }); |
useMasterKey()についてはこちら、トリガFunctionについてはこちら
完成したらdeployしましょう。
ここまでで「①友達からのメッセージがPush通知でお知らせされる」のためにやることは完了です。
ParsePushBroadcastReceiverを継承したReceiverの実装
次は「②Pushから受け取ったデータを自動的にviewに追加する」を実装していきます。まずAndroid側でParsePushBroadcastReceiverを継承したReceiverを実装します。Push通知を受け取ってのもろもろはこのクラスが処理を行います。
CustomReceiver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class CustomReceiver extends ParsePushBroadcastReceiver { //Pushを受け取った時 @Override protected void onPushReceive(Context context, Intent intent) { Log.d("CostomReceiver", "onPushReceive"); //jsonデータを取り出す try { Bundle extra = intent.getExtras(); String data = extra.getString("com.parse.Data"); JSONObject jsonObject = new JSONObject(data); JSONObject sendMessage = jsonObject.getJSONObject("sendMessage"); Message receiveMessage = new Message(sendMessage); EventBus.getDefault().post(new CustomEvent(receiveMessage)); } catch (JSONException e) { e.printStackTrace(); } } } |
jsonデータの受け取りに関しては、こちらを参考にしました。
別途Messageクラスを作り、CustomReceiverで受け取ったJsonObjectをList<Message>としてAdapterに渡しています。
Message.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class Message { private String username; private String body; public Message(String username, String body) { this.username = username; this.body = body; } public Message(JSONObject jsonObject) { try { this.username = jsonObject.getString("username"); this.body = jsonObject.getString("body"); } catch (JSONException e) { e.printStackTrace(); } } public String getUsername() { return this.username; } public String getBody() { return this.body; } } |
また、Activityの判別をするためにEventBusを使用しました。
CustomEvent.java
1 2 3 4 5 6 7 8 9 10 11 12 |
public class CustomEvent { private Message message; public CustomEvent(Message message) { this.message = message; } public Message getMessage() { return this.message; } } |
EventBusは今回実装にはあまり関与していませんが、Push通知を開いた時など複数のイベントにActionをつけたり、非同期処理の結果を用いて次の処理を行ったりするとなると必要かと思います。
表示用のActivityとカスタムListView用のAdapter
最後に受け取ったメッセージを表示するActivityとAdapterを用意してください。ListViewを想定しています。自分はListViewとViewHolderで勉強しました。
CustomListItemAdapter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public class CustomListItemAdapter extends ArrayAdapter<Message> { private LayoutInflater mLayoutInflater; static class ViewHolder { TextView usernameTextView; TextView bodyTextView; } public CustomListItemAdapter(Context context, List<Message> objects) { super(context, 0, objects); mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.custom_list_item, parent, false); holder = new ViewHolder(); holder.usernameTextView = (TextView) convertView.findViewById(R.id.username); holder.bodyTextView = (TextView) convertView.findViewById(R.id.body); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // リストアイテムに対応するデータを取得する Message item = getItem(position); // 各Viewに表示する情報を設定 holder.usernameTextView.setText(item.getUsername()); holder.bodyTextView.setText(item.getBody()); return convertView; } } |
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public class MainActivity extends ActionBarActivity { private CustomListItemAdapter customListItemAdapter; //EventBusを登録 @Override protected void onResume() { super.onResume(); EventBus.getDefault().register(this); } //EventBusを消す @Override protected void onStop() { EventBus.getDefault().unregister(this); super.onStop(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ListViewに表示するデータを作成する List<Message> list = new ArrayList<>(); Message message = new Message("username","body"); list.add(message); ListView listView = (ListView) findViewById(R.id.ListView); customListItemAdapter = new CustomListItemAdapter(this, list); listView.setAdapter(customListItemAdapter); } public CustomListItemAdapter getMessageAdapter() { return this.customListItemAdapter; } //Receiverから呼ばれ、メッセージをViewに追加する public void addMessage(Message message) { getMessageAdapter().add(message); getMessageAdapter().notifyDataSetChanged(); } //EventBusにより発火されるイベント public void onEvent(CustomEvent customEvent) { addMessage(customEvent.getMessage()); } } |
MainActivityを開いている時にPushが来ると、addMessage関数でListViewに追加していくといった具合です。
メッセージを送ってみる
これでメッセージの受信は完璧です。しかし、今回はメッセージの送信 (Messageテーブルへの保存) は実装していません。おとなしくAPIを叩いてMessageテーブルに保存します。
1 2 3 4 5 6 |
$ curl -X POST \ -H "X-Parse-Application-Id: $PARSE_APP_ID" \ -H "X-Parse-REST-API-Key: $PARSE_REST_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"username": "yutaro", "body": "hoge"}' \ https://api.parse.com/1/classes/Message |
こんな感じにできれば完成
まとめ
今回は通知を受け取った時の処理についてまとめました。LINEなどのメッセンジャーアプリの内部がどうなってるのかはわかりませんが、websocketでサーバーサイドを実装しなくても、Pushをトリガーに (見た目上は) リアルタイム更新っぽいことができます。
ちなみに
- 通知を受け取った時 :onPushReceive
- 通知を開いた時 :onPushOpen
- 通知を解放した時 :onPushDismiss
- 通知自体のカスタマイズ:getNotification
をオーバーライドすれば、それぞれのイベントに対しても処理を追加できます。
注意点としてReceiverの中で重い処理はできないようです。
非同期処理をしようとするとフリーズして落ちます。
onPushReceiveやonPushOpenで新しいActivityをスタートさせるのが、正しい使い方のような気がします。新しいActivityをスタートさせる方法についてはこちらが参考になります。
今回のソースコードはGitHubに上げたので、参考にしていただければ幸いです!!!
おわりに
初ブログは研修で行ったAndroidについてでしたが、配属後はiOSアプリ開発に携わることになりました 携わっています。業務ではObjective-Cですが、SwiftもOSS化したタイミングなので、こっちも触っていきたいですね!
今回の記事についてのご指摘やご質問等ありましたら@yutailang0119までお願い致します:)
2015年新卒入社のひよっこエンジニア。 図らずも新規事業チームでiOSアプリ開発を担当することに。Pythonにも興味を示しています。 ひよっこなりに、身に付けた成果を発信して行ければと思います。