2012年6月17日日曜日

Dreamweaver CS6 + PhoneGap 1.7

表題の通り、DW CS6を使い始めています。まぁ無料評価版ですが。なんかものすごい重いけど、それはまぁ最新のMacなら問題ないのでしょう。

で、今日はさくっと簡単にアプリをiPhoneの上で走らせて、そこまでの紆余曲折なんかも公開して、「いやー、ちょっとハマりましたけど、簡単ですね。はははははは」とか書こうと思っていたのですが、最後の最後、iPhoneにインストールするところで失敗してしまいます。

Androidはエミュレータ上で普通に起動するんですが。

というわけで、表題のDreamweaver CS6 + PhoneGap 1.7>iPhone4については、また今度の週末チャレンジ致します。

それにしても、これまでPhoneGap苦手ってかJavaScript苦手なんであんまり触っていなかったんですが…覚えることがたくさんあり過ぎて涙目です。

いや、まだ別にPhoneGapで行くと決めたわけではないのですが。

2012年6月9日土曜日

Salesforce季節の変わり目にありがちなこと:APIが違う

さてもうすぐSummer '12です。

新規に取得したDev環境はSummer '12になってますね。その上で一部リリースされた新機能を試したりチャットのポップアップがうぜぇwなどと言いつつ作業し、できあがったパッケージを本番環境に移そうとしたのですが…「API違うから駄目っすw」と言われました。

パッケージもForce.com IDEも駄目。同じことをSpring '12の時期にもやっていたような気がする…でも、Dev環境取得する時点でAPI選べないのでどうにもなりません。

で、エラーを細かくチェックしていくと、

  • カスタムオブジェクトやレイアウトに関しては移植可能
  • エラーが出るのはVisualforce, Apex, Triggerなどのメタファイル

という状態でした。

ということでwork around。

  1. 稼働環境用サンドボックスへカスタムオブジェクトやレイアウトをすべて移植
  2. 同サンドボックスへ手動でVisualforce, Apex, Triggerをコピペ
  3. サンドボックスから本番環境へDeployment
まぁ当たり前の手順ですけども、ハマった方のご参考になれば幸いです。

なお、今回ソース数本だったので手動で対応できましたが、本数が多いと依存関係もあって大変です。とりあえずは以下の順番でコピペしてみます。
  1. Trigger
  2. 通常のApex
  3. Visualforce
  4. テストコード
ひっかかったら適宜入れ替えます。また、どうしても依存関係が解消できない場合には、先にクラス名定義だけコピーしておいて、あとから実装部分をコピーするという方法もあります。最後の手段として、ソースの数だけブラウザにタブを開いておいて、依存関係のないところから片っ端からQuick Saveして少しずつ埋めていく、なんてのもあります。

申し遅れましたが、この手の作業はForce.com IDEよりもブラウザ経由の方がレスポンスが良くて作業しやすいような気がします。

2012年5月10日木曜日

Chatter APIにまつわる愚痴

会社で作業していると目的のAPIがなかなか出てこない。自宅で作業していると同じGoogle先生を使っているのに「FeedItem」でサクッとREST APIが出てくる。

何ででしょう? 昨日30分ぐらい「あれ、Chatterのメッセージにコメントがいくつついているかを返すメンバーって何だっけ? Comments?」ってGoogle先生をお供にさまよっていたんですが…今検索したら一発でFeedItemのCommentCountが出てきました。

見つかれば文句ないんですけどねぇ…。

それにしても個人で提出したiPhoneアプリ。今までの経験から「審査待ちまですくなくとも1週間」ってのはわかっているんですが、それにしても待ち遠しい。

2012年5月6日日曜日

Native iOSアプリをChatterの「いいね!」に対応

■FourChatter絶賛改良中■

Chatter上の文言をサクっと検索する倉橋屋謹製アプリFourChatterを改良してます。今更ですが。主な改良のポイントは以下の2点:
  1. iPad対応
  2. 「いいね!」の実装
iPad対応は、同じGUIそのままならそんなに苦労はないけれど、このFourChatterは「iPhoneの向きを変えるだけでキーワードを切り替えて検索できる」ってのがウリです(電車の中ではちょっと恥ずかしいけどね)。が。iPadで向きを変えるのは結構ウザい。というよりも、五十肩の私にはしんどい。というわけで、Segmented Controlを導入しました。

…iPhone版もこれでいいんじゃね? Segmented Controlなら4つに限らなくても良いんじゃない??という声もありますが。


■「いいね!」の実装■

Chatterの「いいね!」はFeedLikeというオブジェクトを使ってます。NewsFeedまたはFeedCommentへのto one relationshipになっていて

  • CreatedByID - 「いいね!」をクリックしたユーザのID
  • FeedItemID - 「いいね!」の対象となるNews Feed ID
  • FeedEntityID - 「いいね!」の対象となるFeedまたはCommentのID
  • InsertedById - このオブジェクトを作ったユーザのID
もともとFeedEntityIDはなかったと思うけど、最近FeedCommentへの「いいね!」がサポートされたことにともなって追加された、ような気がします。


■まず「いいね!」はこんな感じ■

SFRestRequest *requestInsert;

NSString *fId = [feed objectForKey:@"Id"];

NSDictionary* dic = [NSDictionary 
    dictionaryWithObjectsAndKeys:fId, @"FeedItemId", nil];

requestInsert = [[SFRestAPI sharedInstance] 
    requestForCreateWithObjectType:@"FeedLike" fields:dic];
[[SFRestAPI sharedInstance] send:requestInsert delegate:self];

これでOK。実行結果は例によって以下が呼び出されます。

- (void)request:(SFRestRequest *)request didLoadResponse:(id)jsonResponse ;
- (void)request:(SFRestRequest*)request didFailLoadWithError:(NSError*)error ;
- (void)requestDidCancelLoad:(SFRestRequest *)request ;
- (void)requestDidTimeout:(SFRestRequest *)request ;

要するに「いいね!」をしたいfeedのidを"FeedItemId"にセットしたNSDictionaryを用意して、それをFeedLikeとしてinsertするだけです。


■いいね!を取り消すには■

上記で作ったFeedLikeを削除します。最初、SELECTで該当するFeedItemIDとCreatedByIDを持つFeedLikeを検索しようとしたのですが、FeedLikeは直接fetchできないというエラーが出ました。ので、NewsFeedをfetchする時に、一緒にFeedLikesもfetchしておきます。

NSString *strQuery = [NSString stringWithFormat:
     @"SELECT Id, (SELECT Id, CreatedById From FeedLikes) 
       From NewsFeed Where Id = '%@' Limit 1", fId];

で、FeedLikesの中から自分のUser IDと同じCreatedByIdを持つFeedLikeを探し出して消します。

fId = (NSString *)[feedLike objectForKey:@"Id"];
requestDeleteLike = [[SFRestAPI sharedInstance] 
    requestForDeleteWithObjectType:@"FeedLike" objectId:fId];
[[SFRestAPI sharedInstance] send:requestDeleteLike delegate:self];

とても簡単。なお、自分以外のユーザの「いいね!」を消せるかどうかは試してません。

2012年5月5日土曜日

Force.com SDK iOS - ログインUser IDの取得

小ネタですが。

Force.com mobile SDK for iOS Nativeアプリで、ログインしているユーザのIDを取得するには、

AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *userId = [[[app coordinator] credentials] userId];

てな感じで。SFOAuthCoordinatorSFOAuthCredentialsは、他にも使えるメンバーを持っているのでリファレンスをブックマークしておくと良です。

で。上記で取得したのは15文字のuser idなので、18文字のCreatedByIDと比較する場合は

if ([createdById hasPrefix:userId]) { ... }

と書かないとダメです。

--

余談ですが。

考え事をしながら引き出しから爪切りを取り出し、床に広げた新聞紙に座り込んでさて爪を切ろうかと思ったらUSBメモリだった。そんな経験をした人はこの世の中に何人ぐらいいらっしゃるのでしょうか。
色とサイズは似ているけど…

2012年4月7日土曜日

Androidアプリを書いて見た。



会社でAndroidアプリを書いてみました。PhoneGapなどに頼らないで、nativeなforce.com対応アプリ。書いたと言っても、Force.comのテンプレートをベースにして、Chatterとコメントを表示するだけですが。環境を整えるのに2時間、Chatterとコメントが出るようになるまで1時間。

で、感想:

  • エミュレータが超重い
  • APIなどが単純でわかりやすい
  • エミュレータが糞重い
  • やっぱりEclipseはJavaで使う分には開発環境だな
  • エミュレータが馬鹿重い
  • iPhoneのInterfaceBuilder最強(今はXcodeに統合されてるけど)
  • エミュレータが劇重い
ってところですかね。「また別のAPI習得するのは私の脳には無理だからPhoneGapにしとこう」と思ってたんですが、いつまでも好きになれないJavaScriptよりも手に馴染んだJavaで単純なAPIを相手にしている方がラクかなとも思ってます。

ツールの圧倒的な操作性の良さとAPIの美しいiOS、単純なAPIとクラッシュしても必ずエラーが出てくれるAndroid。自宅ではiPhone、会社ではAndroid…まぁ良い感じかな。

で、問題のAndroidのエミュレータですが…会社のPC(Core i2, 2GB,HD)が終了しなくなってしまうのは何故。

とにかくエミュレータには困ったもんです。Android x86入れたくても会社のPC(しつこいが今時Core i2, 2GB,HD)じゃ無理す。7980円の有線LAN仕様Android買うかなw

追記(2012/04/10):
在宅勤務用のCore i5, 3GB, Windows7だとエミュレータは何事もなかったかのように無事終了してくれます。今後Android開発はこっちでやろうっと。

2012年3月28日水曜日

iPhone対応Force.comアプリをiPadでも


■iPhoneアプリをiPad対応に■

Force.com iOS SDKのnativeテンプレートは、iPhone / iPad両方に対応しています。

ただ、iPad版はMaster / Detailが前提。しかし私の作ったFourChatterはChatterがソースなもんで必ずしもきっちりMaster / Detailがあるわけではなくテンプレートのままの形態では動かすことができません。というわけで、当初はdisabledしてました。

しかしそのままというのも何だし、このところまったく開発が止まってしまっているのも何なので久しぶりに手をいれてiPad対応にしてみることにしました。


■経緯■

新しくiPad用のStoryboardを作り、プロジェクトに登録。 iPhone用のStoryboardを別ウィンドウで開いておいて、見比べながらviewをぽちぽち置いていく地味な作業を約30分。とりあえず、iPad上でもRoot Viewが動くようになりました。

で、StoryboardにCommentViewを追加したら…見事にハマりました。

…ってか、このブログハマってばっかりだよなorz


■症状■

症状としては、RootViewだけでは動いていたのにCommentViewをStoryboardに追加しただけでRootViewすら出てこなくなりました。SIGABRTです。そこから約2時間、Assembler上をシングルステップで追いかけてみましたが、どーにも見つかりません。念のためiPhoneで試してみると、動きます。 まぁこの場合、動かなかったら泣きますけどね。


■解決■

最終的な原因は、テンプレートのAppDelegate.mにありました。ここで「iPadだったらsplitViewをどうたら」という処理をしています。そうです、そんなものはとっくの昔に消しちまいました。ということで、この辺をコメントアウトして無事動くようになりました。

で…リリースしようと思ったんですが…FourChatterのウリは「iPhoneの向きを変えるだけでさくっとキーワードを切り替えてChatterを検索できる」っていうところにあります。しかし、iPadをぶんぶん回すとバカみたいという問題があります。私のiPad(初代)は回転ロックしちゃってるしね…。

というわけで、やっぱりiPhoneにはiPhone、iPadにはiPadに向いたGUIがあるよなぁ…というのが結論です。

さて、どうしましょう。タブで切り替えるか、任意の個数のキーワードを登録してタップで切り替えられるようにするか…。ただ任意の個数ってことになると「Four」Chatterっていうアプリ名が意味不明になるわな(4方向のfourです)。

--

でも今四十肩が痛くて新しいGUIのあるべき姿、みたいなことが考えられない。

…これを「四十肩ぐらいでw 言い訳にも程があるww」と思ったヒトは、本当の四十肩の怖さを知らないのだよ。寝返り打つたびに目が覚めるし、何かにつまずいてうっかり手を撞こうものならその場にしゃがみ込むほど痛いし。消炎鎮痛剤効かないし。今も何もしてないのに痛いし。

……以下、記事本体よりも長くなりそうなので省略。とにかく痛いのだ。

この記事を、とび職なのに四十肩でも仕事を休まなかった亡父に捧げます。

四十肩には効かないけど便利なので愛用

2012年3月18日日曜日

MySQLとDatabase.com

■予告と違いますが■

以前からぼちぼち書いていたこっちの記事が先に出来上がったので公開します。


■そのまま比較するのはいくら何でもアレですが■

今まで15年ぐらいMySQL使っていたと思うのですが、今日はじめてMySQL Workbenchを使ってみました。例によってマニュアル読まないのでとっつきにくかったけど、馴染んでしまえば楽勝。やっぱローカルサーバはサクサク動いていいですわ。

Salesforceは、というか、やっぱりWebアプリって、どうしてもサクサク感が足りない。日本にデータセンターできてWebでのレスポンスはよくなったものの、それでも「query」っていうレスポンス感ではないように思います。

Amazon RDSなどクラウド上で動いているMySQLサービスなんてのもあるけど、あれはどうなんでしょ。使ったことのある方いらっしゃいます? まぁ素のqueryとユーザ権限などてんこ盛りにかぶさったForce.com APIを同列に比べてはいけませんけども。


■Force.comからmySQLへ■

先日、Force.com APIで動かしていたFlashアプリをMySQLに移行するというお仕事をしました。一応、互換レイヤを作っておいてサクっと移行のはずですが予期せぬデータ変換トラブルが出るのは業界のお約束でございます。「お約束」ではすまない、とても大変なことになったのですが、その辺は長いので省略。関係者の皆様に改めて御礼とお詫びを申し上げる次第でございます。

その経験を元に、FlashでForce.comあるいはmySQLをアクセスしまくる方法、について簡単にまとめてみたいと思います。


■使用ライブラリ■

以下のライブラリが必要です。swcをダウンロードしてAIRプロジェクトのlibsに入れます。



■Connection/AsyncResponder■

割と似てます。当然Force.comではサーバ指定とポート指定は不要でid, passwordを渡してやればつながります。

mySQL:
mysql = new com.maclema.mysql.Connection("<サーバ>", 3306, "<接続名>", "<ユーザ>", "<パスワード>");
mysql.addEventListener("connect", handleConnected);
mysql.addEventListener("ioError" , errorConnected);
mysql.connect();

..

private function handleConnected(e:Event):void {
    trace( "connection success" );
}

..

private function errorConnected(error:Event):void {
    trace( "connection error" );
    trace( e.toString() );
}

Force.com:
var lr:LoginRequest = new LoginRequest();
lr.username = "<force.com id>";
lr.password = "<password><signature>";

lr.callback = new com.salesforce.AsyncResponder(loginHandler, faultHandler);
force.login(lr);

..

private function loginHandler(result:LoginResult, inObject:Object):void {
    trace("login success");
}

..

private function faultHandler(result:Fault):void {
    trace(result.faultcode);
    trace(result.faultstring);
}

何かするごとにAsyncResponderで成功時と失敗時のコールバック関数を渡してやるのも同じです。

Force.com/mySQLを一本化する場合に一つ問題があります。それは、asSQLはmx.rpc.AsyncResponderを使いますが、Force.comは同じ名前のcom.salesforce.AsyncResponderを使うという点。名前が違うだけなら、宣言と生成でフルパス指定すれば済むんですが、困ったことにコールバック関数の引数の数が違います。もちろん1本のソースコードでmySQLとforce.comに対応、なんてことをやらなければ全然問題ないんですけども。


■Fetch■

MySQL:
var st:Statement = mysql.createStatement();
st.executeQuery("SET NAMES 'UTF8'");
st.sql = "SELECT Id, Name FROM SomeTable__c";
var token:MySqlToken = st.executeQuery();
token.addResponder(new mx.rpc.AsyncResponder(queryHandlerSomeTable, fault, token));

..


private function queryHandlerSomeTable(data:Object, token:Object):void {
    var rs:ResultSet;
    rs = ResultSet(data);
    while( rs.next() ) {
        trace(rs.getString("Id"));
        trace(rs.getString("Name"));
    }
}


Force.com:
strQuery = "SELECT Id, Name FROM SomeTable__c";
force.query(strQuery, new com.salesforce.AsyncResponder(queryHandler, faultHandler));


..


private function queryHandlerSomeTable(result:QueryResult):void {
    if (result != null && result.records != null && result.records.length > 0) {
        for each (var item:SObject in result.records) {
            trace(item.Id);
            trace(item.Name);
        }
    }
    if (!result.done) {
        force.queryMore(result.queryLocator, 
                        new com.salesforce.AsyncResponder(queryHandlerSomeTable,
                                                          faultHandler));
    }
}



単純なfetchなら話は簡単でSOQLとSQLもまったく同じになります。問題はリレーションを使う場合です。force.comはオブジェクト型なので  Account.Name って書くだけで取引先名を引っ張ってこれますが(WebObjectsもそうだったなぁと遠い目)、mySQLはJoinで表を結合してAccount.Name には適当なエイリアス名を付けて参照する必要があります。このへん需要があれば詳しく書きますのでコメントください(ない、というオチが寂しい)


■Update/Insert

需要があれば書きます。


■共通の落とし穴■

fetchした結果はどっちもObject型みたいなもんに入って返って来ます。で、どっちもSQL/SOQLのattribute名とObjectから引っ張り出す時のkeyが間違っていると、fetchしたのにnullだったのか、SELECT文に書き忘れてnullだったのか、すぐにはわかりません。大文字と小文字を間違えてもダメです。SELECT文に書いた通りのkeyと完全に一致しないとダメです。Objectにkeyがあるかどうかを確認して、あるはずのkeyがなかったらエラーを出す、というような処理をはさみましょう。

まぁね…当たり前のことなんだけどね…。


■Force.comでのはまりどころ■

Flashとは違ってAIRで使う場合はデフォルトで同期がONになっています。ので、Fetch後に自動的にForce.com APIに対して問い合わせをします。しかし、相手はForce.com、頻繁に問い合わせするもんであっという間に1日のアクセス制限回数を超えてしまいます。ログイン前にでも以下の行を実行してキャッシュ切ってください。

force.doCache = false;


■asSQLの落とし穴■

update文でprimary keyの値を指定しますが…その値の型が間違っていた場合には、「success側のコールバック関数にfailが帰ってくる」という何が何だかわからない現象が起こります。これ気づくのに2時間ぐらいかかりました…だってねぇ…まさかねぇ…。

2012年3月12日月曜日

ご無沙汰しております

サラリーマンが忙しくて、ぜんぜん手が付けられませんでした。

とりあえず「SalesforceエンジニアのためのFlash入門」という需要があるんだか無いんだかまったくわからない連載をスタートさせようと思います。

どうぞよろしくお願いします。

2012年1月31日火曜日

Desktopを見るのが怖い…

いえね、昨日終業間際にDataLoaderを使って急ぎのインポート作業をやったんですけど、なかなかうまくいかなくてですね。

対象オブジェクトは3つ。何だかんだで20回ぐらいリトライしてようやくうまく行ったのです。で、そのままPCをシャットダウンしまして。

Desktopにアイコンが100個ぐらい並んでいるんだろうなー…やだなー…。

--

ところで、開発サンドボックスってレポートは移行してくれないんでしたっけ。昨日それで30分くらい右往左往してました。Force.com IDEでDeploymentすると「Success」で終わるのにフォルダに現れてくれない。foldersも一緒にデプロイしてもダメ。

本筋と関係ないので諦めましたが…。