Murayama blog.

プログラミング教育なブログ

AndroidでParse入門 - Objects -

これは何?

Parse本家のドキュメントをまとめたものです。*1
Android Developer Guide | Parse

今回はObjects編をまとめます。

The ParseObject

ParseObjectにはデータを格納します。ParseObjectはJSON互換のKey-Valueペアを保持します。このデータはスキーマレスであり、事前に定義しておく必要はありません。好きなように設定すればバックエンドに格納されます。

たとえば、ゲームのハイスコアを記録するなら、ParseObjectは次のようなデータを含みます。

score: 1337, playerName: "Sean Plott", cheatMode: false

Keyは、英数字の文字列である必要があります。Valueは、文字列、数値、真偽値、配列などJSONエンコードできるものを利用できます。

ParseObjectはクラス名(class name)を持ちます。これは他のデータと識別するために必要です。たとえば、先ほどの例の場合、GameScoreという名前をつけています。クラス名は先頭を大文字にし、key名は先頭を小文字にすることを推奨します。

Saving Objects

Parseクラウド上にGameScoreを保存する手順をみてみましょう。インタフェースはMapと似ており、saveInBackgroundメソッドがあります。

ParseObject gameScore = new ParseObject("GameScore");
gameScore.put("score", 1337);
gameScore.put("playerName", "Sean Plott");
gameScore.put("cheatMode", false);
gameScore.saveInBackground();

このコードを実行すると、何が起きたのか戸惑うかもしれません。ParseのData Browserを見れば、保存されたデータを確認できます。次のように見えるでしょう。

objectId: "xWMyZ4YEGZ", score: 1337, playerName: "Sean Plott", cheatMode: false,
createdAt:"2011-06-10T18:33:42Z", updatedAt:"2011-06-10T18:33:42Z"

ここで注意すべき点が2つあります。一つは、このコードを実行するまで、クラスを定義する必要がなかったことです。Parseアプリケーションは、必要となったときにクラスを作成するようになっています。

もう一つは、いくつかの便利なフィールドが追加されていることです。objectIdは保存されたオブジェクトの識別子です。createdAtとupdatedAtは、Parseクラウド上で作成されたオブジェクトの作成日時、更新日時を意味します。これらのフィールドはParseによって管理されています。そのためParseObjectが保存されるまでこれらのフィールドは存在しません。

Retrieving Objects

データを保存することは簡単でした。データを取得することも簡単です。objectIdがあれば、ParseQueryを使ってParseObjectを検索することができます。

ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
query.getInBackground("xWMyZ4YEGZ", new GetCallback<ParseObject>() {
  public void done(ParseObject object, ParseException e) {
    if (e == null) {
      // object will be your game score
    } else {
      // something went wrong
    }
  }
});

ParseObjectから値を取り出すには、データ型に合わせたgetXメソッドを使います。

int score = gameScore.getInt("score");
String playerName = gameScore.getString("playerName");
boolean cheatMode = gameScore.getBoolean("cheatMode");

データ型がわからないならget(key)メソッドを利用することもできます。しかしその場合はおそらくCastする必要があるでしょう。ほとんどの場合、getStringのようなタイプアクセサを使う方が良いでしょう。

また、3つの特殊なアクセサがあります。

String objectId = gameScore.getObjectId();
Date updatedAt = gameScore.getUpdatedAt();
Date createdAt = gameScore.getCreatedAt();

取得済みのデータをクラウド上のデータからリフレッシュするなら、fetchInBackgroundメソッドを使用してください。

myObject.fetchInBackground(new GetCallback<ParseObject>() {
  public void done(ParseObject object, ParseException e) {
    if (e == null) {
      // Success!
    } else {
      // Failure!
    }
  }
});

GetCallbackはメインスレッド上で実行されます。

Saving Objects Offline

ほとんどのsaveメソッドは、呼び出すと即座に実行されるため、保存が成功したかどうか判断することができます。保存が完了したか判断する必要がないのであれば、saveEventuallyを代用すると良いでしょう。saveEventuallyメソッドを使えば、ユーザーがネットワークに接続していない場合は、ネットワークが再接続されたときに保存処理を実行できるようになります。また、再接続するまえに、アプリケーションを閉じた場合、次回アプリケーションを起動した際に更新を試みます。saveEventually (deleteEventually)メソッドの呼び出しは、呼び出し順序が記録されるため、複数回呼び出しても安全です。

ParseObject gameScore = new ParseObject("GameScore");
gameScore.put("score", 1337);
gameScore.put("playerName", "Sean Plott");
gameScore.put("cheatMode", false);
gameScore.saveEventually();

Updating Objects

オブジェクトの更新は簡単です。新しいデータをセットしてsaveメソッドを呼ぶだけです。保存済みのオブジェクトとobjectIdがあれば、ParseQueryを使ってParseObjectを取得し、データを更新できます。

ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
 
// Retrieve the object by id
query.getInBackground("xWMyZ4YEGZ", new GetCallback<ParseObject>() {
  public void done(ParseObject gameScore, ParseException e) {
    if (e == null) {
      // Now let's update it with some new data. In this case, only cheatMode and score
      // will get sent to the Parse Cloud. playerName hasn't changed.
      gameScore.put("score", 1338);
      gameScore.put("cheatMode", true);
      gameScore.saveInBackground();
    }
  }
});

Parseは更新データ(ダーティフィールド)を自動的に解析します。更新するつもりのなかったデータが誤って更新されることはありません。

Counters

上記のサンプルにはよくある事例が含まれています。"score"フィールドは、ユーザの最新スコアを保持するために継続的にカウントアップされます。先のメソッドを使っても更新処理は実装できますが、手間はかかりますし、複数のクライアントが一つのカウンターを更新する場合は不具合を招くかもしれません。

Parseは、カウンタータイプのデータを格納をサポートするために、数値型フィールドに対してアトミックなincrement(decrement)処理を提供しています。更新処理をは次のように実装できます。

gameScore.increment("score");
gameScore.saveInBackground();
You can also increment by any amount using increment(key, amount).

Arrays

配列データを格納を支援するために、アトミックな変更をサポートする3つの操作を提供しています。

  • add、addAllメソッドは、配列フィールドの最後尾に要素を追加します。
  • addUnique、addAllUniqueメソッドは、配列フィールドに存在しない要素であれば要素を追加します。ただし、挿入位置は保証されません。
  • removeAllメソッドは、 配列フィールドから指定されたすべてのオブジェクトを削除します。

たとえば、"skills"のような項目に要素を追加する場合は次のようになります。

gameScore.addAllUnique("skills", Arrays.asList("flying", "kungfu"));
gameScore.saveInBackground();
Note that it is not currently possible to atomically add and remove items from an array in the same save. You will have to call save in between every different kind of array operation.

Deleting Objects

Parseクラウドからオブジェクトを削除する場合は

myObject.deleteInBackground();

削除時のコールバックが必要な場合は、deleteInBackgroundメソッドにDeleteCallbackを渡すことができます。呼び出しもとのスレッド(calling thread)をブロックしたいなら、deleteメソッドを使用できます。

removeメソッドを使ってオブジェクトの特定のフィールドを削除することもできます。

// After this, the playerName field will be empty
myObject.remove("playerName");
 
// Saves the field deletion to the Parse Cloud
myObject.saveInBackground();

Relational Data

オブジェクトには他のオブジェクトとリレーションを定義することができます。リレーションは、ParseObjectの値として他のParseObjectを保持することで実現します。内部的には、Parseフレームワークは、参照先との整合性を保つために一つの領域にオブジェクトを格納します。

例として、ブログの1件のPostに対していくつかのCommentがつく場合を考えてみます。新しいPostに対して、1件のCommentがつく場合、次のように実装します。

// Create the post
ParseObject myPost = new ParseObject("Post");
myPost.put("title", "I'm Hungry");
myPost.put("content", "Where should we go for lunch?");
 
// Create the comment
ParseObject myComment = new ParseObject("Comment");
myComment.put("content", "Let's do Sushirrito.");
 
// Add a relation between the Post and Comment
myComment.put("parent", myPost);
 
// This will save both myPost and myComment
myComment.saveInBackground();

また、objectIdを利用してオブジェクトとリンクすることもできます。

// Add a relation between the Post with objectId "1zEcyElZ80" and the comment
myComment.put("parent", ParseObject.createWithoutData("Post", "1zEcyElZ80"));

デフォルトでは、オブジェクトをフェッチした時点では、リレーション先のParseObjectはフェッチされません。これらのオブジェクトの値は、明示的にフェッチするまで取得することができません。

fetchedComment.getParseObject("post")
    .fetchIfNeededInBackground(new GetCallback<ParseObject>() {
        public void done(ParseObject object, ParseException e) {
          String title = post.getString("title");
        }
    });

ParseRelationオブジェクトを使えば多対多のリレーションを定義することができます。これは、Listとよく似ていますが、一度にリレーションに含まれるすべてのParseObjectをダウンロードする必要はありません。ParseRelationは、Listのアプローチと比べて、たくさんのオブジェクトをスケールしやすいようになっています。たとえば、UserはたくさんのPostにlikeをつけることを考えてみましょう。この場合、getRelationメソッドを使って、Userの好きな(likeな)いくつかのPostを保存することができます。PostをListに追加する代わりに、次のように実装します。

ParseUser user = ParseUser.getCurrentUser();
ParseRelation<ParseObject> relation = user.getRelation("likes");
relation.add(post);
user.saveInBackground();

ParseRelationからPostを削除する場合は次のようにします。

relation.remove(post);

デフォルトでは、リレーション内のリストオブジェクトはダウンロードされません。getQueryメソッドの戻り値であるParseQueryのfindInBackgroundを使えば、Postのリストを取得できます。コードは次のようになります。

relation.getQuery().findInBackground(new FindCallback<ParseObject>() {
    void done(List<ParseObject> results, ParseException e) {
      if (e != null) {
        // There was an error
      } else {
        // results have all the Posts the current user liked.
      }
    }
});

Postのサブセットだけを取得したいなら、getQueryメソッドの戻り値であるParseQueryに制約を施すこともできます。コードは次のようになります。

ParseQuery<ParseObject> query = relation.getQuery();
// Add other query constraints.

ParseQueryに関する詳しい情報は、本ガイドのQuery節を参照してください。ParseRelationはListとよく似ています。List内のオブジェクトを照会するようにParseRelation上でも実行することができます。

Data Types

ここまではString、int、bool、そしてParseObjectといったデータ型を扱いました。Parseは、java.util.Date, byte[], JSONObject.NULL型もサポートしています。

JSONObjectやJSONArrayを用いて,一つのParseObjectの内部に構造化されたデータを保持することもできます。

int myNumber = 42;
String myString = "the number is " + myNumber;
Date myDate = new Date();
 
JSONArray myArray = new JSONArray();
myArray.put(myString);
myArray.put(myNumber);
 
JSONObject myObject = new JSONObject();
myObject.put("number", myNumber);
myObject.put("string", myString);
 
byte[] myData = { 4, 8, 16, 32 };
 
ParseObject bigObject = new ParseObject("BigObject");
bigObject.put("myNumber", myNumber);
bigObject.put("myString", myString);
bigObject.put("myDate", myDate);
bigObject.put("myData", myData);
bigObject.put("myArray", myArray);
bigObject.put("myObject", myObject);
bigObject.put("myNull", JSONObject.NULL);
bigObject.saveInBackground();

Parseは、ParseObjectのbyte[]フィールドにイメージや文書のような大きなバイナリを保持することは推奨していません。ParseObjectは128kbを超えるべきではありません。大きなデータを保存したい場合はParseFileの利用を推奨しています。詳細ない情報はParseFile節を参照してください。また、Parseがデータをどのように管理しているかを知りたいなら、Data & Securityドキュメントを参照してください。

Other Operations

Parseは、ParseObjectのデータ管理をサポートするコンビニエンスメソッド(便利メソッド)を提供しています。

Counters

多くのアプリではカウンターが必要となります。たとえば、ゲームのスコア、コインの取得、羊を数えるなど。Parseはカウンターをアトミックに更新する方法を提供しています。

ParseObject player = new ParseObject("Player");
player.put("goldCoins", 1);
player.saveInBackground();
 
// Later ..
player.increment("goldCoins");
player.saveInBackground();

increment(key, amount)のように加算値を指定することもできます。

*1:翻訳というほどの品質ではないです。。間違ってたらごめんなさい