40歳からのキャリアチェンジ

20代はエンジニア・PM、30代はWeb系エンジニア向けのキャリアアドバイザー。40代の今はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連あたり

Titanium + Alloy + napp.alloy.adapter.restapiで作る簡単Qiitaビューワーアプリ

最近、技術系のネタをQiitaに集中していたのですが、ブログのアクセス解析の状況見てたらTitanium Mobile  alloy」「ACSとは」 という意図したもので流入しはじめてきたので、Alloyネタをこっちにも書いておきます。

こんな感じの簡単Qiitaビューワーアプリを Alloy + napp.alloy.adapter.restapiで作る方法について簡単に解説します

shot-2014-10-18-13.44.08.png

自分の環境

開発前の事前準備

Alloy用の RestAPI Sync Adapter の配置

  1. PROJECT_FOLDER/app/assets配下に、alloyフォルダを作成して、その中にsyncフォルダを作成します
  2. Alloy用の RestAPI Sync AdapterというのがGitHubにあるのでダウンロードして、上記作成したsyncフォルダに配置します。最終的には、PROJECT_FOLDER/app/assets/alloy/sync/restapi.js

alloy.jmkの準備(任意)

自分はCoffeeScriptで書くことが多いので、PROJECT_FOLDER/app/alloy.jmkを作成して、以下のように記述してます。

task("pre:compile", function(event,logger) {
  logger.info('pre compile start!');
  var wrench = require("wrench"),
      fs = require("fs"),
      path = require("path"),
      controller_root = event.dir.controllers,
      model_root = event.dir.models,
      coffee = require("coffee-script");
  wrench.readdirSyncRecursive(controller_root).forEach(function(controller){
    if (controller.match("coffee$")) {
      fs.writeFileSync(
        path.join(controller_root,controller.replace("coffee", "js")),
        coffee.compile(
          fs.readFileSync(path.join(controller_root, controller)).toString(),
          { bare: true }));
    }
  });

  wrench.readdirSyncRecursive(model_root).forEach(function(model){
    if (model.match("coffee$")) {
      fs.writeFileSync(
        path.join(model_root,model.replace("coffee", "js")),
        coffee.compile(
          fs.readFileSync(path.join(model_root, model)).toString(),
          { bare: true }));
    }
  });
});

task("post:compile",function(event,logger){
  logger.info('compile finish');
});

PROJECT_FOLDER/appのフォルダ構成で関連しそうなところだけ抜粋すると以下のようになります。

├── alloy.jmk
├── alloy.js
├── assets
│   ├── alloy
│   │   └── sync
│   │       ├── restapi.js
│   ├── android
│   ├── blackberry
│   ├── iphone
│   └── mobileweb
├── config.json
├── controllers
│   ├── index.coffee
│   └── index.js
├── models
│   ├── qiita.coffee
│   └── qiita.js
├── styles
│   └── index.tss
└── views
    └── index.xml

実際の開発の流れ

WebAPIと連携するアプリ開発はTitaniumの得意な所かと思うのですが、Alloy用の RestAPI Sync Adapterを使うことで、これまで以上に開発が捗るかと想います。

実際の開発手順を順番に示していきます。

QiitaのWebAPIにアクセスするためのModelの開発

RestAPI Sync Adapterを使うことで、Web APIとの連携処理がだいぶスッキリ書けるようになります。

Alloy用の RestAPI Sync AdapterのHow To Useを見ればおおまかに利用方法がわかるかと思いますがシンプルにアクセスするだけというような状況でしたら

  1. Web APIのエンドポイントURLを指定
  2. collection_nameを指定

という所がポイントになります。

今回は、

という形にして、app/models/qiita.coffeeを以下のように作成しました。

exports.definition =
  config:
    URL: "https://qiita.com/api/v1/items"
    adapter:
      type: 'restapi'
      collection_name: 'qiita'
    extendModel: (Model) ->
      _.extend Model.prototype, () ->
        return Model
        
    extendCollection: (Collection) ->
      _.extend Collection.prototype, () ->
        return Collection

注)Modelのファイル名とcollection_nameは同じ形にしておかないとエラーになります。

Controllerを作成

Controllerから上記作成したModelを呼び出すには

qiitaItems = Alloy.createCollection 'qiita'
qiitaItems.fetch
  success: () ->
    # 任意の処理
  error: () ->
    # エラーの時の処理

という形にします。今回は簡易的なビューワーアプリを目指して作ったので、QiitaのWebAPIからパブリックな投稿情報を取得して、TableView使って表示することにしたので以下のようにしました。

$.index.open()

$.activityIndicator.show()
qiitaItems = Alloy.createCollection 'qiita'
qiitaItems.fetch
  success: () ->
    $.activityIndicator.hide()
    _stringify = JSON.stringify(qiitaItems) # 注1
    items = JSON.parse(_stringify)
    return refreshMainMenu items

  error: () ->
    $.activityIndicator.hide()
    Ti.API.info "error"


# Qiitaから取得したデータをTableViewにセットする処理
refreshMainMenu = (items) ->
  rows = []
  for item in items
    Ti.API.info item.title
    
    row = $.UI.create "TableViewRow",
      classes: "itemRow"
      data: item
      
    titleLabel = $.UI.create "Label",
      text:item.title
      classes: "titleLabel"

    bodyLabel = $.UI.create "Label",
      text:item.raw_body
      classes: "body"
            
    row.add titleLabel
    row.add bodyLabel
    rows.push row
    
  return $.mainMenu.setData rows

注1

本来はstringify→parseは全く意味がない処理なんですが、restapi Adapter使うと何故かわからないのですが、JSONのパース処理でエラーになります。

何か不要な文字か制御コードが含まれているようなのですがJSON.stringifyで文字列化して、その文字列化したものをJSON.parseすることで回避できます。

Viewの生成

取得した投稿情報を表示するためにXMLとTSSを以下のように準備しました。

index.xml

<Alloy>
  <TabGroup>
    <Tab id="tabOne">
      <Window id="mainWindow" class="container" title="Qiita">
        <ActivityIndicator id="activityIndicator" message="Loading.." />
        <TableView id="mainMenu" />
      </Window>
    </Tab>
  </TabGroup>
</Alloy>

index.tss

"#mainWindow":{
    statusBarStyle:0,
    translucent:false,
    navTintColor:"#0066ff",
    backgroundColor:"#fcfcfc",
    tabBarHidden:true
}
"#mainMenu":{
    backgroundColor:"#fcfcfc",
    separatorColor: '#cccccc',
    width:Ti.UI.FULL,
    height:Ti.UI.FULL,
    left:0,
    top:0,
    zIndex:1
    
}

"#activityIndicator":{
    top:"50%",
    left:"20%",
    textAlign:'center',
    backgroundColor:"#222",
    font:{
        fontSize:18
    },
    color:'#fff',
    zIndex:10
}

".itemRow":{
    width:Ti.UI.FULL,
    height:"15%",
    hasChild:true,
    backgroundColor:"#fcfcfc"
}

".titleLabel":{
    width:"90%",
    height:"20%",
    top:"5%",
    left:"5%",
    textAlign:'left',
    color:'#59BB0C',
    font:{
        fontWeight:'bold',
        fontSize:16
    }

}
".body":{
    width:"90%",
    height:"70%",
    top:"25%",
    left:"5%",
    textAlign:'left',
    color:'#222',
    font:{
        fontSize:12
    }

}

最後に

QiitaのようなWeb APIや、Titaniumの開発元が提供してるACSと連携するようなアプリ開発をする場合のサーバーとの通信部分の処理でAlloy+napp.alloy.adapter.restapiを使うことですごくスッキリ書けるように思います。

投稿数を絞って取得したい場合にはクエリーパラーメーターを付与する必要があるのですが、その辺りについてはまた別の機会に書きたいと思います。

参考情報