Titaniumの単体テスト

Ti.Developers.meeting Vol 0.2 : ATNDでお見せしたコード。

A test library for Titanium Mobile — Gist

上記をtestwin.jsという名前で保存して、こんな感じで使います。

Titanium.include('testWin.js');
addTest('とあるテストの実施ボタン',function(){
mustequal(getValue(),3,'getValue()は3を返すはず');
});

testwin.png

addTestを呼び出すたびに画面にテスト実施ボタンが追加されていきます。なので、unittestを書くような感覚で、思いついたテストをサクサクaddTestすればテスト用アプリが出来るよ、というもの。テスト結果については、mustequal(二つが一致していなかったらエラー扱いにする)とか、musttrue(変数がtrueじゃなかったらエラー扱いにする)という関数が用意されていて、結果をTi.APIのログに出力します。

ある程度大きな規模でAndroidとiPhone両方で動くものをつくろうと思ったら、データ構造とUIの分離が必要ではないかな、と考えていて。UIはもう諦めて別々につくる。データ構造については、両者共通のものが作れるはず。
で、データ構造部分を作るに当たっては、unittest的なものがほしいよね、ということで書いてみたものです。

マクドナルドで15分くらいで書いたものなので、結果出力が美しくないとか、unittestだったら自動実行してほしいとか、色々思いつく点はあるのですけど、まあとりあえず今あるものを公開してみたというスタンスです。
ライセンスは、TitaniumMobileと同じApache License, Version 2.0ということにしておくので、思うところがある方は自由にいじってみてくださいませ。僕も使っていくうちに色々直すだろうから、いいものができたらまたUpdateしようと思います。

[titanium]Basic認証を通す

sxchu_910074_mirage.jpg

TitaniumMobileでBASIC認証を通すのに、HTTPCientのsetBasicCredentialsという関数が使える、という記述を時々見かけます。guides_network_httpclient – titanium-mobile-doc-jaとか。Titanium Mobileで開発するiPhone/Androidアプリにも載っています。

でもこれ、どうも今使えないっぽい。

var xhr = Titanium.Network.createHTTPClient();
xhr.setBasicCredentials('username', 'password');
xhr.onload = function() {
Ti.API.info('xhr.onload');
};
xhr.onerror = function(e) {
Ti.API.info('xhr.onerror '+e.error);
};
xhr.open("GET","http://example.com/hoge.html");
xhr.send();

setBasicCredentialsが使えるのであれば、上記のようなコードでうまくいくはずなのですけど、1.7.1iPhoneで走らせると、

[ERROR] Script Error = invalid method '(null)' at app.js (line 2).

そんな関数は知らんぜ、と言われます。実際、Titanium.Network.HTTPClientを見ても、そんな名前の関数は存在しません。1.5位まではさかのぼってみたんだけど、その頃からなかったっぽい。

最初っから存在しなかったらWikiに書かれることもないはずなので、たぶん過去のある時点では存在したのだと思います。バージョンが変わったら関数が一個なくなるくらい、TitaniumMobileの利用者は慣れっこですよね?(涙)

なお、setBasicCredentialsが使えない状態でどうやってBASIC認証を通すかについては、Antelope Love Fan — Basic Authentication with Titanium.Network.HTTPClientが参考になりました。

var xhr = Titanium.Network.createHTTPClient();
xhr.onload = function() {
Ti.API.info('xhr.onload');
};
xhr.onerror = function(e) {
Ti.API.info('xhr.onerror '+e.error);
};
xhr.open("GET","http://example.com/hoge.html");
var authstr = 'Basic ' +Titanium.Utils.base64encode(username+':'+password);
xhr.setRequestHeader('Authorization', authstr);
xhr.send();

openしてからsetRequestHeaderを呼び出すのがポイントです。

「Titanium Mobileで開発するiPhone/Androidアプリ」


Titanium Mobileで開発するiPhone/Androidアプリ 索引

日本初のTitanium Mobile本「Titanium Mobileで開発するiPhone/Androidアプリ (Smart Mobile Developer)
」が届きました。
titanium-mobile-doc-jaを主催されている@donayamaさんの本ということで、中身を見るまでもなくAmazonで予約注文したのですけど、期待を裏切らない出来上がりです。

Titanium Mobileで開発するiPhone/Androidアプリ (Smart Mobile Developer)

Titanium Mobileで何がめんどくさいかといえば、iPhoneとAndroid両方で動くことを念頭に置くと、あっちで動くものがこっちで動かない、こっちで動くものはあっちで動かない、となることです。
Appcelerator社のエバンジェリストになった増井さんですら、「感覚的にはソースコードの7~8割を共有し、2~3割を対象のOSごとにチューニングしていくようなイメージになる」
言われているくらいなので、そもそも両方で動くものを書こうとするのが間違いなのですけど、書籍や記事のサンプルコードなどは出来れば一個ですませたいですし、分けるにしたって、どこまでが共通で動いてどこからを分けるかを考える時点でかなりめんどくさかったりするのです。

ということで、「めんどくさいからサンプルはiPhoneのみ対応、Androidについては最後に一章もうけて説明するだけ」とかでも仕方ないのかな、と思っていたのですが、中身を見ると、最初から最後までちゃんと両対応で書かれています。
もちろん、iPhoneとAndroidは別のOSなので、片方でしか動かない機能や、片方だけでやる必要のある作業もあるのですけど、それについては一目で分かるマークをつけて解説されていました。
これだけの量のサンプルを、iPhoneとAndroid両方で動くようにチェックするのは相当の手間だったはず。でも、Androidのアプリを開発したい人にしてみれば、iPhoneのおまけみたいに扱われたら残念な気持ちになってしまいますから、これはとても良いことだと思います。

Titanium Mobileで開発するiPhone/Androidアプリ「Androidのみ」

twitterアプリの章では、拙作「tm_twitter_api」を使った例も紹介されています。
つい先日Androidにも対応したのですが、書籍を書かれた時点ではまだリリースされていなかったため、書籍では、donayamaさん自身がパッチを当てたサイトへのリンクが紹介されていました。
そんな状態なのに、作者として「古川大輔氏」と名前を載せていただいていて、なんだか申し訳ないくらいです。

Titanium Mobileで開発するiPhone/Androidアプリ 古川大輔氏

おまけとして、TitaniumMobileの簡易日本語APIリファレンスもついていて、これからTitanium Mobileを触ってみる方には大変お買い得だと思いますので、まだの方はぜひどうぞ。

Ti.includeの不具合が1.7で修正される模様

aa.png

Titaniumのインクルードパスを指定する方法私案で話題にしていた、サブディレクトリ内にあるファイルがTi.includeした時どこを見に行くか問題ですが、1.7で修正が入るみたいです。

[#TIMOB-3349] iOS: Ti.include() not handling relative
pathing properly - Appcelerator JIRA

さっそく1.7RCで実験してみました。

app.js
Ti.include('lib/test.js');
lib/test.js
Ti.include('lib/a.js');
lib/a.js
Ti.API.info('I am lib/a.js');
lib/lib/a.js
Ti.API.info('I am lib/lib/a.js');

こういうプロジェクトを用意して、Titanium Mobile SDK1.62とTitanium Mobile SDK1.7RC、それぞれiOS向けにビルドして走らせてみます。

1.6.2
[INFO] Compiling JavaScript...one moment
:
[INFO] Application started
[INFO] mogyatest1/1.0 (1.6.2.878906d)
[INFO] I am lib/a.js

 1.62では、Resourceからの絶対パスでファイルを参照しています。

1.7RC
[INFO] Compiling JavaScript...one moment
:
[INFO] Application started
[INFO] mogyatest1/1.0 (1.7.0.RC1.e6afca8)
[INFO] I am lib/lib/a.js

1.7RCでは、includeしたファイルを基準とした相対パスでファイルを探すようになりました。Androidと同じ挙動です。

Resource
 └lib/test.js
 └lib/a.js

上記のような構成で、test.jsからa.jsを参照したい場合、1.6.2までは

  • iOS : Ti.include(‘lib/a.js’);
  • Android : Ti.include(‘a.js’);

と使い分けるか、Titaniumのインクルードパスを指定する方法私案で紹介したような方法で工夫する必要がありました。
1.7からは、iOSもAndroidと同じ方法でincludeが出来るようになるので、普通に書けばiOSでもAndroidでも必要なファイルをincludeすることが出来ます。

逆に、iOSしか使わないことを前提に、Ti.include(‘lib/a.js’)と書いていた方は、1.7でビルドし直すと動かなくなることが予想されます。また、僕のブログを参考にして、

var path_lib = 'lib/';
if (Ti.Platform==null || Ti.Platform.osname=='android'){
path_lib = '';
}
Ti.include(path_lib+'a.js');

と書かれていた方も、1.7では動かなくなります(iOSが正だと思っていたので)。

1.6.2、1.7共通で動くコードを書くのであれば、

var path_lib = '';
if(Titanium.Platform.osname == 'iphone' && Titanium.version<'1.7.0'){
path_lib = '/lib/';
}
Ti.include(path_lib+'a.js');

という具合にバージョン番号を見るようにする必要がありますね。

Androidのデモをする時はAndroid Screen Monitorが超オススメ

tm_twitter_apiの紹介デモで使ったら一部の方から感心していただいたのでご紹介。

プレゼンの途中でスマートフォンの画面を見せたい時、iPhoneだったらiPhoneエミュレーターで見せればいいのですけど、Andoroidの場合、エミュレーターが遅すぎるので、実質的に使い物になりません。だからといってちっちゃなスマートフォンの画面をそのまま見せたってお客さんには見えないので、結構困りますよね。

そういうときに、Android Screen Monitor がオススメです。画面をリアルタイムで PC 上に表示してくれるツールで、これを使えば実機でアプリを動かしている様子をPC経由でプロジェクタに表示することが出来ます。

Android Screen Monitor-3.png

iPhoneエミュレーターと並べてデモをすると、後ろから見ている方にはほとんど区別がつかないみたいで、昨日のプレゼンのあとも、「あの超早いAndroidエミュレーターは何ですか?」と聞かれましたw

JAVAで書かれているので、AndroidSDKを導入済みの方であれば、ダウンロードしてパスの通った位置に置いて「java -jar asm.jar 」で動くはず。公式サイトを見るとより詳しい導入方法が書かれています。

TitaniumMobile用Twitterライブラリtm_twitter_api

sxchu_1087539_11462380_friends.jpg
TitaniumMobileでTwitterを扱うときに便利なtm_twitter_apiの新バージョンを公開しました。

mogya/tm_twitter_api – GitHub

  • Androidでも動くようになりました!
  • twitterのUIが変わって動かなくなっていたので、新しいUIで動くようにしました

いずれも他の方が書いたパッチを取り込んだものです。オープンソースって素晴らしいですね。


使い方

Ti.include("lib/twitter_api.js");
//initialization
Ti.App.twitterApi = new TwitterApi({
consumerKey:'YOUR CONSUMER KEY of twitter API',
consumerSecret:'YOUR SECRET of twitter API'
});
var twitterApi = Ti.App.twitterApi;
twitterApi.init();

こんな感じで初期化します。CONSUMER KEYとSECRETは、Twitterの開発者ページでアプリ登録したときにもらえるものです。
twitter_app_page.png

twitterApi.init(); を初めて呼び出したときは、ユーザーさんにOAuthのための画面が掲示されます。ユーザーさんがアカウント名とパスワードを入れると、TwitterがPINコードというのを返してきますが、これはtm_twitter_apiが勝手に読み込んで認証処理が終了します。
initがすんだら、twitterのAPIを呼び出すことができます。とりあえずつぶやいてみましょう。

//status update
twitterApi.statuses_update({
onSuccess: function(responce){},
onError: function(error){},
parameters:{status: 'yah! this is my first tweet from twitter_api.js! '}
});

 つぶやくときは、Twitterのstatuses/updateというAPIをつかうので、tm_twitter_apiでは、twitterApi.statuses_update関数を呼び出します。
うまくいったときはonSuccess、なにか問題が起きたときはonErrorのコードが呼び出されます。
つぶやきの内容など、APIに渡す引数はハッシュparametersにいれて渡します。
この構造は、全てのAPIに共通です。

//get tweets
twitterApi.statuses_home_timeline({
onSuccess: function(tweets){
for(var i=0;i<tweets.length;i++){
var tweet = tweets[i];
// now you can use tweet.user.name, tweet.text, etc..
}
},
});

つぶやきを取得するコードです。onSuccessでtweetsという引数が渡されてくるので、これをループで回すと、つぶやきを取得することができます。

AndroidはGaraxySしか試せてないので、タブレット端末とかだと変わった動きをするかもしれません。どんなことになったか教えていただけると嬉しいです。
もちろんパッチも歓迎です(笑)