Umi Uyuraのブログ

プログラミング関連の作業ログ

Alloy Modelの学習・その4(REST API)

引き続き、Alloy Modelを勉強中。

umi-uyura.hatenablog.com

クロスプラットフォームなUIを素早く構築しつつWeb APIと連携するというのは、Titaniumが活用できるパターンだと思います。

というわけで、今回はREST APIとのData Bindingを試してみました。

REST API Sync Adapter

標準で用意されているSync Adapterは、SQLite用のSql AdapterとTitanium.App.Propertiesを使うProperties Adapterの2種類しかありませんが、Sync Adapterは自作することもできます。

Custom Sync Adapters - Alloy Sync Adapters and Migrations

REST API用のSync Adapterを作ってくれている人がいますので、これを採用することにします。

viezel/napp.alloy.adapter.restapi

今回作るもの

アプリ自体は簡単にするため、 その2 で作ったものと同じ仕様とします。

それとは別に、連携先のAPIが必要です。

適当なWeb APIを使っても良かったのですが、CRUDを一通り触りたかったのと、サーバー側に渡ってくるリクエストの内容も把握したかったので、今回はスタブAPIサーバーを立てることにしました。

スタブAPIサーバー

スタブAPIサーバーについては、シンプルで使いやすそうだったMockyを使ってみることにしました。

okv/mocky: http mocking server with simple config written on nodejs

テーブル

スタブなので実際のデータベースは用意しませんが、 その2 と同じ仕様ということで、以下のようなテーブル構造を扱うAPIを用意します。

内容 カラム
レコードの識別子 id INTEGER
メモの内容 contents TEXT
メモの優先度 priority TEXT

API

上記のテーブルをもとに、CRUDをひととおり呼び出せるよう、以下のようなAPIにすることにしました。

操作 Method URL
メモを読み込む GET http://127.0.0.1:4321/memos
メモを追加 POST http://127.0.0.1:4321/memos
メモの内容を更新 PUT http://127.0.0.1:4321/memos/{ID}
メモを削除 DELETE http://127.0.0.1:4321/memos/{ID}

スタブAPIPサーバー自体の実装は省略しますが、ダミーデータをメモリ上に保持しておいて、APIの呼び出しに合わせてダミーデータを操作しています。

例えばメモの読み込みAPIを叩くと、こんな感じにJSONのレスポンスを得られる感じです。

f:id:umi-uyura:20160227114353p:plain

データベースを使っていないので、サーバーを再起動するとデータは初期状態に戻ります。

ソースは umi-uyura/AlloyModelStudyRestStub にありますので、こちらをご覧ください。

Qiitaにあった mocky を使ってちょっと賢いスタブAPIサーバを作る - QiitaNode.js の mocky を使用し スタブREST API を作成してみる、その1 - Qiita あたりを読めば簡単に作れました。

アプリ

アプリ側のソースは umi-uyura/AlloyModelStudyRestApi です。

プロジェクト作成

毎度おなじみCLIでプロジェクト作成。

$ titanium create --type app --id com.example.titanium.alloymodelstudyrestapi --name AlloyModelStudyRestApi --platforms iphone,android --url http://www.example.com --workspace-dir .
$ alloy new AlloyModelStudyRestApi

これをベースに、今回もUIはJade & Stylusに、FokkeZB/UTiL/xp.ui を使っています。

REST API Sync Adapter (napp.alloy.adapter.restapi) の準備

GitHubリポジトリから restapi.js をダウンロードして、プロジェクト内の app/assets/alloy/sync/ へ配置しておきます。

モデル作成

先の想定テーブル構造から、ベースとなるモデルを生成します。このとき、 restapi タイプというのは Alloy コマンドがサポートしていないため、いったん sql として生成しました。

$ alloy generate model memo sql 'id:integer' contents:text priority:text

生成されたモデルファイルをもとに、REST API用に編集します。

exports.definition = {
  config: {
    columns: {
      "id": "integer",
      "contents": "text",
      "priority": "text"
    },
    adapter: {
      type: "restapi",
      collection_name: "memo",
      "idAttribute": "id"
    },
    "URL": "http://127.0.0.1:4321/memos",
    "debug": 1
  },
  extendModel: function(Model) {
    _.extend(Model.prototype, {
    });
    return Model;
  },
  extendCollection: function(Collection) {
    _.extend(Collection.prototype, {
      comparator: function(data) {
        return (0 - data.get('priority').length);
      }
    });

    return Collection;
  }
};

編集した箇所としては、以下のとおりです。

  • config.adapter.typerestapi に変更
  • URL を追加し、スタブAPIのURLを設定
  • debug を追加、これでAPIを使う際のリクエスト/レスポンスがデバッグ出力されてわかりやすい

他に headersparentNode をといった設定もありますが、今回は未使用。

以下はデバッグ出力の例。

f:id:umi-uyura:20160227114411p:plain

Data Binding

ビューの部分は 前回 と同じもので、コントローラもほぼ一緒で動きました。

一箇所修正したのは、メモを追加したあとに呼び出していた memos.fetch() の部分で、通信が発生してタイミングが変わるせいか、これを呼び出してしまうと追加したはずのメモが消えてしまう現象が発生しました。

その上の行の memos.add(m) までで、ListViewには新しいメモが追加されていたので、 memos.fetch() は削除することにしました。

app/controllers/index.js

...

function addText() {
  var txt = $.memoText.getValue();
  var params = {
    contents: txt,
    priority: ''
  };

  var m = Alloy.createModel('memo', params);

  m.save();
  memos.add(m);
  //memos.fetch();      <== 削除した

  $.memoText.setValue('');
}

...

エラーハンドリングについて

今回やっていないこととして、通信のエラーハンドリングがあります。

データ構造を同じにしたこともありますが、SQL用からREST API用にAdapterを入れ替えて、ほぼそのまま動かせてしまいました。

しかし、ローカルにあるデータベースなどと比べて、インターネットを介した通信は不安定だったり、APIサーバー側で何らかの問題が発生することなども考えられます。

そのため、napp.alloy.adapter.restapiには、以下のような感じで通信成功時とエラー時で処理をハンドリングできるようになっています。

var collection = Alloy.createCollection("MyCollection"); //or model
//the fetch method is an async call to the remote REST API.
collection.fetch({
    success : function(){
        _.each(collection.models, function(element, index, list){
            // We are looping through the returned models from the remote REST API
            // Implement your custom logic here
        });
    },
    error : function(){
        Ti.API.error("hmm - this is not good!");
    }
});

napp.alloy.adapter.restapiを使ってきちんと実装する場合は、この点も考慮する必要があると思います。

参考

モックサーバー

Appcelerator Titanium Smartphone App Development Cookbook - Second Edition

Appcelerator Titanium Smartphone App Development Cookbook - Second Edition