TitaniumでParse（特にAndroid）

書きかけで放置していたネタを埋め草で放出するよ！

本当は「AndroidでWindowのopenイベントの後もActionBarのMenuItemを操作するとエラーになる」という小ネタもあったんだけど。

# TitaniumでParse

[Titanium](https://platform.appcelerator.com/)のAndroidアプリで[Parse](https://parse.com/)を使う場合、選択肢は大きく分けて２つあります：

1. [JavaScriptでREST API](https://github.com/iantearle/Ti.Parse/blob/master/ti.parse.js)を叩く
2. モジュールからネイティブのSDKを利用する

いずれも一長一短があります。比較してみましょう。

## JavaScript版のいいところ

* 簡単、かつソースコードも短く、内容をすべて把握することができる
* REST APIで実現できることはなんでもできる
* Queryの結果はそのままAlloyのCollectionとして扱ってData Bindingも利用できる

## JavaScript版の問題点

* ログイン時の認証情報がGETリクエストで送信されてしまう([!](https://gist.github.com/stephenfeather/4400387#file-parse-js-L131))ので、スマートフォンのように外出先でよくわからないアクセスポイントから通信する場合は致命的

というわけで、結構いけそうなのに、肝心なところで深刻な問題があるようです。ではモジュールの方はどうでしょう。

## モジュール版のいいところ

* Parseの提供する機能はなんでも使える
* 認証も安全に実行できる

## モジュール版の問題点

* ~~そもそもAndroid版なんか存在しない~~Android用モジュールは古くなってしまったので下位互換性のない変更が入った後のPush通知に対応していない
* [修正案は放置されている！](https://github.com/ndizazzo/android-parse-titanium-module/issues/24)
* Queryのように複雑な機能を抽象化して汎用的なモジュールにするのはかなり難しい
* Data Bindingを利用することができない
* 画像データをサブクエリで取得するとbyte[]型のデータを全部取得してから処理することになるので遅い

…もう投げ出したいですね。

でも、よく見てみると、これらの一長一短はそれぞれ補完しあう関係にあることがわかります。JSの最大の問題点（セキュリティ）はモジュール側で対応可能、モジュールの最大の問題点（柔軟性がない）はJSで書いてしまえばいいのです。古来より選択肢が２つある場合は常に「両方です」と答えるべしと相場は決まっており、北斗と南斗、紀香と菜々子、ビアンカとフローラなど歴史的事実に照らし合わせても具体例は枚挙に暇がありません。われわれは、たいていの場合は別にどちらか一方を無理に選択しなくてもいいのです。

## 解決方法

認証についてはネイティブのSDKを使ったモジュールを用意し、クエリについてはJSを使うのがベストの組み合わせです。[iOSはこちらのモジュール](https://github.com/ewindso/ios-parse-titanium-module)を利用できます。Androidは…自作しちゃいましょう。

ParseのAPIは、ログイン・サインアップすると各ユーザにsessionTokenと呼ばれる文字列が渡されます。有効期間内にこの文字列を添付してアクセスすることで、Android SDKの利用するAPIとREST API間で同一ユーザとして相互にやり取りすることが可能になります。

[AlloyのREST API adaptor](https://github.com/viezel/napp.alloy.adapter.restapi)を使ってクエリを発行してみましょう。

例えば、ネイティブのモジュールから[新規登録](https://parse.com/docs/android/guide#users-signing-up)や[ログイン](https://parse.com/docs/android/guide#users-logging-in)を呼び出すと、Parse上のユーザーとして登録・ログインすることができます。そのあと、[現在のユーザー情報を呼び出し](https://parse.com/docs/android/guide#users-current-user)てsessionTokenを取得します。ネイティブ側のモジュールは例えばこんな感じになります。

```java
  @Kroll.method
  public KrollDict getUserInfo(){
    KrollDict user = new KrollDict();
    ParseUser currentUser = ParseUser.getCurrentUser();
    try {
      currentUser.fetch();
      user.put("nickname", currentUser.get("nickname"));
      user.put("email", currentUser.get("email"));
      user.put("sessionToken", currentUser.getSessionToken());
      user.put("objectId", currentUser.getObjectId());
    } catch (ParseException e) {
      user.put("success", false);
    }
    return user;
  }
```

これをJS側から呼び出すと、Parse上のユーザー情報を取得できます。

```js
var parse = require('my.mod.parse');
var user = parse.getUserInfo();
```

この`user`オブジェクトにはsessionTokenが含まれるので、これを使ってREST APIを叩くと、このユーザーとしてクエリを発行することができます。その際、[AlloyのREST API adaptor](https://github.com/viezel/napp.alloy.adapter.restapi)を使う場合はbeforeSendでParse特有のヘッダを設定することができます。

```js
var collection = Alloy.createCollection('post');
collection.fetch({
  url: url,
  beforeSend: function(xhr){
    xhr.setRequestHeader('X-Parse-Application-Id', YOUR_APP_ID);
    xhr.setRequestHeader('X-Parse-REST-API-Key', YOUR_REST_API_KEY);
    xhr.setRequestHeader('X-Parse-Session-Token', user.sessionToken);
  },
  success: function(e){}//略
});
```

もちろん、modelの中でheadersとしてこれを設定してしまうことも可能です。

```js
exports.definition = {
  config: {
    "URL": "https://api.parse.com/1/classes/Post",
    "adapter": {
      "type": "restapi",
      "collection_name": "posts",
      "idAttribute": "objectId"
    },
    "headers": { // your custom headers
      "Accept": "application/json",
      "X-Parse-Application-Id": YOUR_APP_ID,
      "X-Parse-REST-API-Key": YOUR_REST_API_KEY,
      "X-Parse-Session-Token": Ti.App.Properties.getString('sessionToken')//Propertyで共有します
    },
    "parentNode": "results" //your root node
  },//略
```

　実際、Parseからデータを取得するだけならREST APIの方が手慣れたやり方を使えて楽なので、認証だけモジュールにして、あとはJSで処理するのがいいのではないかと思います。

　ご要望があれば認証のAndroidモジュールを公開しておこうかな。いや、簡単なんですよ。例えば新規ユーザー登録はざっくり

```java
//モジュール側
@Kroll.method
public void signUp(KrollDict args, final KrollFunction successCallback, final KrollFunction errorCallback){
  ParseUser user = new ParseUser();
  user.setUsername(args.get("username").toString());
  user.setPassword(args.get("password").toString());
  user.setEmail(args.get("email").toString());
  user.signUpInBackground(new SignUpCallback(){
    public void done(ParseException e) {
      KrollDict data = new KrollDict();
      if(e == null){
        successCallback.call(getKrollObject(), data);
      }else{
        data.put("message", e.getLocalizedMessage());
        errorCallback.call(getKrollObject(), data);
      }
    }
  });
}
```

```js
var parse = require('my.mod.parse');
parse.signUp({
  username: username,
  password: password,
  email: email
}, function(e){
  //success
}, function(e){
  //error
  alert(e.message);
});
```
みたいな感じで動きます（必須項目チェックしてないけど）。

FacebookやGoogle+などサードパーティーの認証ベンダを利用することもできます。

Facebookモジュールのauthorizeメソッドを実行して認証が完了すると、loginイベントが発生します。この時、uidなどの情報が渡されるので

```js
$.facebook.addEventListener('login', function(e){
  var authData = {
    id: e.uid,
    accessToken: e.source.accessToken,
    expirationDate: e.source.expirationDate
  };
});
```

この情報を利用して[ParseのREST APIで既存ユーザと紐付けたり、新規ユーザとして登録する](https://parse.com/docs/rest/guide#users-linking-users)こともできます。また、このやり方で作成したユーザ情報にはaccessTokenが含まれるので、これを使ってネイティブのモジュールを利用するためには、Parseに用意された[become](https://parse.com/docs/android/guide#users-setting-the-current-user)メソッドを呼び出します。

```java
ParseUser.becomeInBackground(sessionToken, new LogInCallback() {
  public void done(ParseUser user, ParseException e) {
    if (user != null) {
      // 紐付け成功
    } else {
      // なんか失敗
    }
  }
});
```

以上、駆け足で見ていきましたが、要するに「認証はネイティブで、クエリはJSで」というのが一番楽なやり方だな、ということでした。