ひとりハッカソンでCakePHP+TwitterAPIアプリ
@mon_sat です。
PHP Matsuri でのハッカソンネタ「愛の告白 Power of Love」ができるまでをチュートリアル形式で。
(チュートリアルとしては詳細が書かれていないのですが・・・)
まずは、何を作るか考えた
さて。ハッカソン。
何を作ったら良いかというところを考える。
過去作ったやつをCakePHPで修正したい気持ちは満々なのですが、せっかくなので1から作りたいですよね。
そこで、作りたいものリストの中からハッカソン期間中に作れそうなものをピックアップし、最終的に「告白」をするアプリに決めました。
これは、単に(このアプリ上から)「つぶやくと、一定期間次のつぶやきができなくなる」というTwitterアプリです。
アプリ名を「愛の告白」とすれば、送られた側は、そのアプリ名を認知することで、ああ真面目な告白なんだと分かるというもの。
ま、基本的にはネタですよね。
ですが、一応以下の選考基準に則って選びました。
- ハッカソンの期間中に「完成」すること
- 分かりやすいこと
- 仕様がある程度はっきりしていること
今回はあくまで完成にこだわりました。
中途半端なものをつくっても、その後完成させるのは、私の性格では無理なので、ハッカソンのみで完成させることを第一に考えました。
多少機能は少なくても良いと割り切るしかありません。
次に「何をするアプリで」「何が嬉しいか(楽しいか)」が伝わりやすいものにしました。
どういう形式で発表するかは分かりませんでしたが、最悪140文字で紹介できるような単純なものを作ろうと思いました。
最後に、難易度の高いものは控え、過去の経験と現在ある知識で製作可能なものにしました。
そのため、サービス内容が固まっていて、仕様を固めやすいものにしました。
結果的に残ったのは一つでしたが。
仕様を決める
サービスの仕様を固めなくてはなりません。
あまり大風呂敷を広げすぎるとたためなくなるのはいつものこと。今回ばかりは多少時間が余る程度のものを検討します。
まず、サービスの核として
- TwitterのOAuthを利用してログイン
- TwitterAPIを利用して投稿
- 投稿時に「次につぶやくまでの時間」を設定しDBへ保存
- 上記の期間内に投稿されたらバリデーションエラー
ここまでを決めてました。
その後、あったら嬉しいor面白いものおよびやりたいことを列挙します
あったら嬉しいor面白いもの
- つぶやきは吹き出し風に表示したい
- フォローしている人を選択できるようにしたい
- バリデーションエラーを凝ってみたい
やりたいこと
- CSS3使って(意味も無く)アニメーションさせたい
- Ktai Library 使って(意味も無く)ケータイ対応させたい
- 国際化してコアデベロッパーにも使ってもらいたい
だいたいこんなところでしょうか。
この時点では、モデルの数もビューの数もぼんやりとしかありません。
その辺は作りながらで平気なボリュームなので、今は決めないのが、オレ流。(良い子はまねしないほうが良いです)
クイズです。さて、次にしたことは何でしょう?
正解は、プレゼンの内容を考える、です。(誰も当たらないな、この俺俺クイズw)
えっと、今回の場合、ハッカソンのためのアプリという面があり、さらに、当面のゴールは発表です。
発表の方法はおそらくプレゼンでしょう。
というわけで、真面目にネタアプリを作るためには、ゴールであるプレゼンを意識しようと思いました。
(ちなみにこの時点では10分以上はあるだろうという能天気なことを考えていましたw)
前回のイメージがあるので、コアデベロッパーも聞いてくれているという認識であれこれ想像してみました。
実はこれを考えているときが一番楽しく、3日くらい帰宅時にはこればっかり考えていました。
で、大体こんな感じ
- コアデベロッパーいるから資料は英語で作ろう。もちろん話すのは無理。
- デモも英語版を使おう。
- デモはiPhone Simulator でやるのはどうか?
- KeyNoteとかいうおしゃれなやつは持ってないから、最近使ってるNerineでいこう。
-
最初にポストするのは何にしよう?嫁の名前をつぶやいて、有名人の名前でバリデーションエラーを起こそうか。
- その場合(インパクトがあるのは)誰だろう?AKB48とか?広瀬香美とか?マツコデラックスとか?
- 会場にいる女性からというのも良いかな?でも仲の良い子がいないなあ。
-
I Love PHP っていいかもね。みんなPHPerな訳だし。
- その場合は I Love Perl かな?@dankogai宛につぶやくとか(笑)
- ハッカソンなのに「ガラケー対応しています。誰得だけど」とか良いかも。
- バリデーションエラー時は「愛が足りません」みたいな感じが良いかも。
あれ?後から考えると別に面白くないね。
でもまあ、つまり、できる限り小ネタを入れていこうと。
まあこの辺はあまり仕様に影響しませんが、ただ、開発の優先順位は変わりますよね。
最後何か対応できないってなったときに、何から削っていくのか。そのとき、プレゼンで影響の薄いところやサービスの核ではないところは切る覚悟ができます。
そして開発へ
いろいろ考えて開発は事前に済まそうと考えました。
理由は
- セッションを聞きたい
- コアデベロッパーとたっぷり交流したい
- 人がいても開発できるような集中力は無い(いつもひとり)
- 酒飲んだら寝る。確実に
- スタッフ業務手伝えてないから、当日ぐらいできることしたい
というわけで、9/29(水)の10~18時で作ることに。
1日で終われば良いが、8時間弱はタイトなので、最悪10/1(金)の10~18時を使おうということで。
合計16時間なので、まあぎりぎりハッカソン中につくるのと一緒ですから(笑)
その模様はTwitterでハッシュタグつけてつぶやいていました。
まとめはこちら。
というわけでスタートです。
まずは、Twitterでアプリの登録をします。
そして華麗にConsumer Key とConsumer Secret をゲット。
まずはtwitterでConsumer key & secret をget #phpmatsuri
http://twitter.com/mon_sat/status/25839176701
Twitter API か OAuth用のプラグイン等を探します。教えてグーグル先生!(今からかい!と自分で突っ込み)
過去Google AppEngine / Python にて、簡単なTwitterアプリは作ったことがあります。
そのときの、OAuthとTwitter API の知識があるとはいえ、結構楽観的です。(笑)
というわけで、でてきたのがこちら。OAuth Consumer Component。
(ただ、PHP Matsuri で@nojimage さんがTwitterプラグイン作ってたことを知りました。Twitterで聞いたらそれを使えたかも)
事例はあるので、さくっと共用のvendorsディレクトリへ放り込みます。シンボリックリンクでapp/vendors内にリンクを作成します。
(cake/vendorsやapp/vendors内に直接DLで普通は何の問題もありません。ただ私の場合こうすることでシンボリックリンクごとgitで管理できるのでこうしています。ローカルもサーバーも同じディレクトリ構成にして)
で、アクセストークンを取得・・・取得・・・取得、できない。
AccessToken が取得できん・・・ #phpmatsuri
http://twitter.com/mon_sat/status/25845347460
最初はリクエストトークンが返ってこなかった。修正後もいろいろ試行錯誤したが、どうやってもアクセストークンが取得できない。
リクエストトークンは取得されている。コールバックまで返ってきている。
何だ?
というわけでソース見て、返ってきたjsonを表示させつつ、試行錯誤。
結局、駄目だったのはURL。
サンプルコードからtwitter APIのURLが変更されている主に、httpsのところとapi.twitter.comのところ。
この時点で、URLはbootstrap.phpにすべて書こうということで、変更した。
# config/bootstrap.php
Configure::write('tw' ,array(
'request_token' => "https://api.twitter.com/oauth/request_token",
'authorize' => "https://api.twitter.com/oauth/authorize?oauth_token=",
'access_token' => "https://api.twitter.com/oauth/access_token",
'post' => "http://api.twitter.com/1/statuses/update.json",
'verify_credentials' => "http://api.twitter.com/1/account/verify_credentials.json",
));
開始から2時間後、無事アクセストークンをゲット。
しかし、ポストできない。
AccessToken 取得できた。が、OAuthConsumer Component で post しても Incorrect Signature となる。あれ? #phpmatsuri
http://twitter.com/mon_sat/status/25848412301
その後、試行錯誤してポストできた。
原因はやっぱりURL(だったと思う。記憶が...)
@mon_sat ‚ł«‚½IURLˆá‚Ă½Borz #phpmatsuri
http://twitter.com/mon_sat/status/25849669884
えっと、ポストできたけど(笑)
というわけで、原因を究明する。
この時点でひとりのフォロワーからBOFではないかというありがたい助言が入る。
しかし、BOFが何のことだか分からない。BOMやEOFではないらしい。が、いずれにせよ文字コード周りと当たりを付けて調査するも不発。
そして、TwitterAPIのエラーの内容が、送信する内容によって、Incorrect Signatureだったり、別のエラーだったりするという摩訶不思議。
結果的にはファイルの文字コードを見たらANSIとか書いてあった。なぜ?
日本語なんてほとんど使ってなかったが、とりあえず使ってるファイルをUTF-8で片っ端から保存し直して無事ポスト成功。
@directsearchjp あいうえお
http://twitter.com/mon_sat/status/25853121710
開始から早3時間20分経過。まずい。
そして昼飯後再開。
再開 #phpmatsuri
http://twitter.com/mon_sat/status/25854634041
Bake all
いよいよbakeするかということでまずはDBを作成。
その後に作ったschema.phpより抜粋
# config/schema/schema.php
var $posts = array(
'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
'user_id' => array('type' => 'integer', 'null' => false, 'default' => NULL),
'post' => array('type' => 'text', 'null' => false, 'default' => NULL, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
'date' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
'power' => array('type' => 'integer', 'null' => false, 'default' => NULL),
'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
);
var $users = array(
'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
'twid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 128, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), 'twname' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 128, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
'image' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 1024, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
);
公開サーバー用にスキーマファイルを生成
$ ./cake schema generate
そしていよいよbake x 2。
$ ./cake bake all Welcome to CakePHP v1.3.4 Console --------------------------------------------------------------- App : app Path: /home/example/allapps/love.tklabo.net/local/app --------------------------------------------------------------- --------------------------------------------------------------- Bake All --------------------------------------------------------------- Possible Models based on your current database: 1. Post 2. User
この辺はさくっといけますね。
そして不要なアクションを削ります。
使うのは、Postのindex,view,addのみ。(adminおよびtwitterコールバック等をのぞく)
UsersControllerは、admin用ならいるかもしれませんが、ユーザー用には不要ということで、すべてをPostsControllerで作成することにしました。
この辺り、モデルも含めて、というかモデルですが、いろいろ試行錯誤しています。
後からフィールド名失敗したなあとか思うこともあるけど、まあ適当に。リファクタリング等も含めて今はやらない。
今回は、addアクションから作り始めました。
まずは骨格ということで、通常のバリデーションとして、postの文字数チェックと、powerが数値かどうかのみをチェックします。
この段階で、国際化の準備をする必要があることを思い出しました。
すべての書き出しは __() 関数を使っているのですが、バリデーションメッセージはこの書き方ができません。
というわけで、CakePlusの出番です。
このプラグインは複数の機能が乗っていますが、この中から、マルチバイトで文字数をチェックしてくれるバリデーション機能を追加しているAddValidationRuleBehaviorと、バリデーションメッセージを国際化可能なValidationErrorI18nBehaviorを使います。
わたしの共用のプラグインディレクトリにはすでにCakePlusがあるので、あとはシンボリックリンクをapp/pluginsに貼るだけです。
# model/post.php
var $actsAs = array(
'Cakeplus.AddValidationRule',
'Cakeplus.ValidationErrorI18n' => array( 'withFieldName' => false ),
);
var $validate = array(
'post' => array(
'maxLengthJP140' => array(
'rule' => array('maxLengthJP', 140),
'required' => true,
'allowEmpty' => false,
),
//(中略)
function beforeValidate() {
$messages = array(
'maxLengthJP140' => __("Love words are always short. Please enter strings within 100 characters", true),
'numericSelected' => __("Please select one", true),
);
$this->setErrorMessageI18n($messages, false);
$this->replaceValidationErrorMessagesI18n();
return parent::beforeValidate();
}
無事バリデーションができたので、動作を確認します。
(ただしこの段階ではDBに書き込まないし、当然Twitterにも投稿されません)
その後Twitterへの投稿とDB保存のロジックを加えました。
この時点のaddアクションはこんな感じ(だったはず)です。
# controllers/posts_controller.php
function add() {
if (!empty($this->data)) {
// set
$this->Post->setPost($this->data);
if ($this->Post->validates() && $result = $this->Love->post()) {
$this->Post->data["Post"]["date"] = date("Y-m-d H:i:s", strtotime($result->created_at));
$this->Post->save();
$this->Session->setFlash(__('Your loving tweet has been sent', true));
$this->redirect(array('action' => 'view', $this->Session->read("User.twid")));
} else {
$this->Session->setFlash(__('Your tweet could not be sent. Check your love. Please, try again.', true));
}
} else {
$this->data["Post"]["status"] = $this->Love->readSessionIfExist();
}
$powers = $this->Post->powers();
$this->set(compact('powers'));
$this->set('title_for_layout', " | Tweet");
}
# models/post.php
function setPost($data) {
// format
$post =& $data["Post"]["post"];
$post = $this->stringBeforePost . $post . $this->stringAfterPost($data["Post"]["power"]);
parent::set($data);
}
setの前に、つぶやきの前後に必要な文字列を加えています。
これによりそれらを加えた文字数でバリデーションがかかるので便利ですね。
(最終的にはこの段階で、Bit.lyのAPIより短縮URLを取得し加えていますが)
$this->Love->Post()は、LoveComponent内でTwitterの送信をしていて、エラーの場合はfalseを正常に送れている場合はjsonの戻り値を返しています。
次の行で、TwitterAPIの返した投稿日時をDBに保存するべくモデル内のデータに書き入れています。
(ここには書かれていませんが、Userモデルの処理も後で追加されます)
メソッドがPOSTではないとき(つまりaddアクションをはじめて読み出すとき)の処理ですが。
これは、投稿した結果、TwitterAPIより認証エラーが返ってきた場合に、再ログインさせてからもう一度addアクションに帰ってくるために入れています。
再ログインする前にセッションに投稿した内容を書き入れて、addアクションに戻ってきたときに書き出しています。
※今考えたら、powerフィールドの値は保存していませんね。これ
初日にやったのはあとは画面を作ったくらいです。
画面は過去に作った別件のお遊びのやつを流用しました。
(後で修正した分も上記に追加されていますので、本当はもうちょっと細部は手を入れる必要がありました)
@directsearchjp test3 #ainokokuhaku [Power of Love: 2,592,000p]
http://twitter.com/mon_sat/status/25863635878
ひとりハッカソン終了。進捗40%・・・続きは金曜日 #phpmatsuri
http://twitter.com/mon_sat/status/25866576200
開発2日目
今日も一昨日のひとりハッカソンの続き。18時迄。合計16時間で何が作れるかワクワクすっぞ。 #phpmatsuri
http://twitter.com/mon_sat/status/26032504445
bit.ly で短縮URLを取得してツイートに混ぜる工程から。
例によって共用vendorsディレクトリにコードを突っ込み、シンボリックリンクを貼ります。
http://planetcakephp.org/aggregator/items/2649-new-bitly-class-to-use-with-cakephp-or-by-it-self
# controllers/components/love.php
function shortenUrlFromPostView() {
$this->verifyCredentials();
$twid = $this->Session->read("User.twid");
if (!$this->Session->read("User.short_url")) {
$long_url = Router::url(array('controller'=>"posts", 'action'=>"view", $twid), true);
App::import('Vendor','Bitly', array("file" => "bitly/bitly.class.php"));
$bitly = new Bitly(BITLY_USER, BITLY_API_KEY);
$short_url = $bitly->shorten($long_url);
$this->Session->write('User.short_url', strval($short_url[0]));
}
return $this->Session->read("User.short_url");
}
セッションに書き込まれていなかったらBit.lyより取得し、書き込まれていたらそれを読み出すということにしました。
今回は、ユーザーページのみなので、作成が楽です。
addアクションでPOST後の先頭で上記のメソッドを呼び出して終了です。
1時間弱で実装完了しました。
bit.ly API で短縮URLゲット #phpmatsuri
http://twitter.com/mon_sat/status/26036306558
@directsearchjp test #ainokokuhaku [Power of Love: 60p] bit.ly/bdTNGr
http://twitter.com/mon_sat/status/26037339525
Routingを設定。
# config/routes.php
Router::connect('/', array('controller' => 'posts', 'action' => 'index'));
Router::connect('/logout', array('controller' => 'posts', 'action' => 'logout'));
Router::connect('/add', array('controller' => 'posts', 'action' => 'add'));
Router::connect('/oauth/callback', array('controller' => 'posts', 'action' => 'callback'));
Router::connect('/a/*', array('controller' => 'posts', 'action' => 'view'));
もちろんテストも設定。
(でも今回書いたテストはRoutingだけだなあ。反省)
Userモデルに手を入れます。
流れだけを書くと。
ログインした時点で、Userデータを保存します。
Updateになる場合は、画像URLの変更有無だけチェックし、変更されている場合はupdateします。
(その方がログインするまで古いアイコンが表示され続けます)
そして、つぶやきの投稿時にそれらのデータを読み出し、$this->Post->data['Post']['user_id']等に格納します。
重複エラーも実装しなくてはいけません。
重複エラーは、独自バリデーションで実装します。
# models/post.php
function isDuplicate() {
$conditions = array('user_id'=>$this->data["User"]["id"]);
$recursive = -1;
$recent = $this->find("first", compact("conditions", "recursive"));
return time() > strtotime($recent["Post"]["date"]) + $recent["Post"]["power"];
}
あとは、基本的にバグ修正でした。
ログイン後のリダイレクトがループして上手く行かなかったり、addアクションにリダイレクトさせると、もう一回ログイン処理が走ったりと、ロジックの見直しを繰り返しました。
あとは、時間までデザインを修正します。
CSS3のグラデーションやつぶやきを吹き出し風にするのに苦労しました。
後者は結局できませんでしたが。
あと、バリデーションメッセージも変更しました。
バリデーションエラー時に「愛を確認してください」「愛が重複しています」という表現を思いつき、それを実装しました。
というわけで2日目終了。
アプリとしては16時間で完成しました。
イベント当日は、手が空いたら、デザインの修正等を行うということで、この日は作業終了です。
実装できなかった機能
- フォローしている人を選択できるようにしたい
- Ktai Library 使って(意味も無く)ケータイ対応させたい
うーん、いつもながら工数を見積もるのが下手だなあ。
当日
この日の開発時点と完成物との差異は、主に国際化とデザインのところです。
国際化
CakePHPは簡単に国際化ができますね。
注意点としては、国際化ファイル(.po)の作成を単純化するのであれば、ある程度サイトが固まってから(書き出すテキストがおおよそ決まってから)の方が良いということですね。
まずは、default.potの作成です。各言語ファイルの元になります。
その後、日本語用のディレクトリを作成し、ファイルをコピーします。
$ ./cake i18n $ mkdir -p app/locale/jpn/LC_MESSAGES $ cd app/locale/jpn/LC_MESSAGES $ cp -p ../../default.pot ./default.po $ vi default.po
poファイルを編集後、サイトにアクセスすると日本語になっているはずです。
このままでは、チェック等が大変なので、英語と日本語を交互に切替可能にしましょう。
まず、以下のアクションを作成し、フッターにそれぞれのリンクをつけました。
# posts_controller.php
function japanese() {
$this->Session->write('love.language', "jpn");
$this->redirect($this->Session->read('love.redirect_url'));
}
function language() {
$this->Session->delete('love.language');
$this->redirect($this->Session->read('love.redirect_url'));
}
function english() {
$this->Session->write('love.language', "eng");
$this->redirect($this->Session->read('love.redirect_url'));
}
# app_controller.php
function beforeFilter() {
if ($lng = $this->Session->read('love.language')) {
Configure::write('Config.language',$lng);
}
}
今回は/posts/languageへのリンクは貼りませんでした。
デザイン
自分自身への課題としてHTML5のバリデーションとCSS3 animationを使ってみようというテーマを持っていました。
まずは、HTML5から。
これはまず、required属性で、つぶやき入力が入力されていなかったら、投稿できないようにしました。
そして、placeholder属性で、入力初期値を規定しました。
それぞれ、コードは次の通りです。
# views/themed/pc/posts/add.ctp
echo $this->Form->input('post', aa('placeholder', __("ex) I Love @mon_sat", true),'required',"required"));
Textareaにplaceholderが使えるかどうかは分かりませんでしたが、Google Chrome ではあっさり使えました。
(他のブラウザは未確認です)
また、投稿後、TwitterAPIが結果を返すまでに数秒かかるので、何らかの方法でSubmitボタンを押されないようにしたいと考えました。
オーバーレイするjQueryプラグインを過去に使ったことがあるので試みましたが、そのプラグインは使えませんでした。
時間もないので、とりあえずdisabledすることで対応。
こういうのも、HTML5で対応して欲しいところですねー。
他は例の無駄にふわふわしているやつですが、それはプレゼン資料に書いてありますのでご参考に。
というわけで完成です。
テスト
自分自身では何度もテストしたのですが、できれば自分以外の投稿が載っていた方が見栄えが良いかなと思い、夜、Grahamを捕まえてテストしてもらいました。
I love #phpmatsuri #ainokokuhaku [Power of Love: 300p] http://bit.ly/bVOVmQ
http://twitter.com/predominant/status/26169095412
というわけで完成です。
Ktai Library 対応とか他にも対応したかったアイディアはあるのですが、そんなことよりも、交流をしようと。
そのために事前に作ったのです。
おかげさまで楽しい夜が過ごせました。
プレゼン
プレゼンの模様は別の記事で書きました。
なので、別の観点より。
わたしは、作ったものを上手にプレゼンすることが苦手です。
そんなわたしが出会ったのが、踊る大捜査線シリーズの監督として有名な本広克行監督のこのつぶやき
> トミ子さんへ、一生懸命に創ったんだから一生懸命に売らないとね‼ (後略)
http://twitter.com/kmotohiro/status/22140801650
わたしはこの言葉にしびれました。
作って終わりではないのですね。作って、それを周知させて(ビジネスなら売り上げを立てて)はじめて完成なのです。
とかく魂込めて作っていると、完成した段階で、魂抜けます(笑)
でも、そこまでで半分なのです。
というわけで今回は。
満足感溢れる自分にむち打って、どうプレゼンするかを考えました。
どう頑張ってもしゃべりは上手くないので、内容を練ります。ひとり作戦会議です。
プレゼン内容は概ね固まっているので、いかにインパクトを与えるかを考えてみました。
その結果。
- デモ中心。説明は極力省く。
- 時間が余ったら解説。
- ということで行こうと。
というのも、技術的に大したことをしていることは無いので、そこはアピールポイントではありません。
ネタで勝負というほど、爆笑をかっさらうネタでもありません。
なので「完成したアプリ」であることを最大限に利用したいと思いました。
その後時間が4分しかないことが分かり、結局デモするだけのプレゼンに(笑)
終わってから考えたこと。
ちゃんと、出る前に、URLをつぶやいたにもかかわらず、その旨を言わなかったのがまず失敗。
そこにアクセスしてくれれば、完成したアプリであるということは自ずと分かってくれたのに。
そして、「最後にこれを言おう」を決めていなかったのが失敗。
それだけ言えれば満足というような何かを言うべきだった。
気づいたらあと20秒弱という状況でしたが、事前に準備していれば話ができたはず。
もうひとつ。
デモでつぶやくときに、ハッシュタグをつけてつぶやかなかったのは最低。
投稿後「つぶやかれているのが確認できる」という言葉も無かった。
とっさに出た「この機会にフォローしてw」で軽く笑いをとれ救われたものの、痛恨のエラーでした。
他の人のプレゼンは参考になるものばかりだったのですが、何よりのけぞったのは@takamunetaniiさんのやつ。
なんと通信が失敗したときに自動的に笑いが取れるようになっていた。
うーん、これは準備の賜物ですねえ。
例えばTwitterAPIが想定外のエラーを返したときとかのバリデーションメッセージは準備しておくべきだったかも。くじらねたの何かとか。
さて、そんなこんなで、愛の告白アプリのできるまでは終わりです。
何かの参考になれば幸いです。
ならなくても半年後の私が参考にするでしょう。
それでは。
See you the next hack with beer !
http://love.tklabo.net/
完成品はこちらです。