Facebook iOS SDK でアプリ招待が動かなくてはまった話
職場の仲間とマッチングサービスを作ってiOSアプリを担当しリリースしました。
簡単にサービスの概要を書くと、1日1回特定のエリア(恵比寿、六本木など)で開催される食事会にエントリーすることができ、同じエリアにエントリーしたユーザとマッチングさせる(ひと昔前に流行ったようなw)サービスです。
このアプリの中ではパートナーとなる友だちがいないとエントリーできない仕組みになっているため、Facebookのアプリ招待機能を実装することになりました。
iOS用のドキュメントに従い、App Link ToolでApp Linkを作成し、以下の実装で招待のダイアログを開きました。
- Xcode: 7.2
- FBSDK: 4.8.0
let content: FBSDKAppInviteContent = FBSDKAppInviteContent() content.appLinkURL = "FacebookのAppLinkToolで作成したAppLink" content.appInvitePreviewImageURL = "任意の画像URL" FBSDKAppInviteDialog.showFromViewController(self, withContent: content, delegate: self)
ところがダイアログの中でユーザを選択して招待を送信してみたところ、被招待ユーザに肝心の招待通知が届きません。
何度もドキュメントと自分の実装を見直したり、ググって解決策を見つけようとしましたが、結局分からず諦めていました。
先日久しぶりに再度招待の実装してみるか、ということで調べ直してみたところ解決したので共有します。
上記のコメントでは以下のように言っています。
Also live App ID from iTunes Connect must be filled in in Facebook App settings
つまり、Facebookのアプリ設定でAppIDも埋まってる必要があるということです。
そこで自分のアプリを確認してみたところ、AppIDの欄はリリース以降更新しておらず空欄のままでした。
そこを埋めて実装は変えず、再度ダイアログから招待を実行してみたところ、無事被招待ユーザに招待通知が届きました。
アプリの1stリリース時にFacebookログイン機能などを実装して(この時点ではまだAppIDが決まっていないため空欄になる)、リリース後にそのまま招待機能を実装しようとしたら同じような状況になる人がいるのではないかなと思います。(まさに僕がそのパターンでした。)
RubyのEnumeratorが理解できたような気がした話
仕事で先人のRubyのコードを見てたらEnumeratorというクラスが出てきました。
初見だったのでググってドキュメントを見てみたがよく分かりませんでした。
特に「遅延評価」てところがパッときませんでした。
(1..Float::INFINITY).map{ |n| n * 2 }.first(5) => 無限リストをループするためfirstまで実行されず処理が終わらない
(1..Float::INFINITY).first(5) => [1,2,3,4,5]
これが遅延評価(どん!)。
どうでしょうか?分かりましたか?
僕は上記ではよく分かりませんでしたが、Enumerator::Lazyを使って手元でコード書いてみてやっと理解できました。
まずはEnumerator::Lazyを使わない例。
[1,2,3,4,5].map do |item| p item item * 10 end.each do |num| p num end => 1,2,3,4,5,10,20,30,40,50
次にEnumerator::Lazyを使った例。
[1,2,3,4,5].lazy.map do |item| p item item * 10 end.each do |num| p num end => 1,10,2,20,3,30,4,40,5,50
こちらの記事を見て理解することができました。
Rubyist Magazine - 無限リストを map 可能にする Enumerable#lazy
あとから知ったんだけど、上記の記事の筆者はEnumerator#lazyの作者だそうな。
CloudWatchでSNSのメトリクス取得につまずいた話
SNSの通知の成功数・失敗数を集計したかったんです。
前任者のコードを見てみたら
// SNSは通知の成功数・失敗数が分からないため0を入れておく
とコメントがあったんだけどそんなことはなくw
CloudWatchでSNSのメトリクスを見ればちゃんと確認できました。
SNSはPush通知で使っているため、iOSとAndroidそれぞれの通知成功数・失敗数が取得したかったのですが
ディメンションでPlatformを指定してもどうにも取得できない。
Amazon Simple Notification Service は以下のディメンションを CloudWatch に送信します。
上記リンクにもこう書いてあるのに…。
でなぜ取得できなかったのか結論から言うと、SNSのTopicを使ってPush通知を送っていたから、でした。
以下は取得できるメトリクスをAWS-CLIで確認した結果です。
TopicNameの方は取れてますが、Platformの方は取れてませんね。
$ aws cloudwatch list-metrics --namespace 'AWS/SNS' --dimensions "Name=TopicName,Value=トピック名" { "Metrics": [ { "Namespace": "AWS/SNS", "Dimensions": [ { "Name": "TopicName", "Value": "トピック名" } ], "MetricName": "DwellTime" }, (省略) ] }
$ aws cloudwatch list-metrics --namespace 'AWS/SNS' --dimensions "Name=Platform,Value=APNS" { "Metrics": [] }
試しにTopicではなくSNSのApplicationに対して通知をしてみたところ、Topicメトリクス以外のフィルタについても確認することができました。
AWSコンソールの表示も以下のように変わりました。
before
after
自作ライブラリをbowerに登録してみた
以前作ったショボいライブラリが会社の人にバレて「npm install、bower installでインストールできるようになるのはまだできない認識であってますか?」って言われて微妙な反応したら
expect(response).to.eq(‘はい、そちらはまだです’);
というチャットが送られてきたのでbowerに登録してみた(パワハラです)
流れ
こんなかんじ。というかこれだけ
1. 登録したいGithubレポジトリを決める
今回登録したのはこれ
以前このブログでも取り上げたので一応再掲
2. bower.jsonを作成する
$ bower init
そしたら対話形式でいくつか質問に答えていけば自動でbower.json作成完了
自宅のbower1.2.7では以下のような質問内容
[?] name: dateformatjs [?] version: 1.0.0 [?] description: Extension of the Javascript Date object with a minimum function for shaping the date. [?] main file: dateformat.js [?] keywords: [?] authors: Toru Furuya t.furuya825@gmail.com [?] license: MIT [?] homepage: https://github.com/torufuruya/DateFormat.js [?] set currently installed components as dependencies? Yes [?] add commonly ignored files to ignore list? Yes [?] would you like to mark this package as private which prevents it from being accidentally published to the registry? (y/N) [?] would you like to mark this package as private which prevents it from being accidentally published to the registry? No
3. Git tagでversioningする
remember to push your Git tags!
本家にこれしか書いてなかったのでやり方書いてくれればいいのにとか思いつつ
$ git tag v1.0.0 $ git push origin v1.0.0
version指定はsemver方式でやれ、みたいなことが書いてあるんだけど要は vX.Y.X のフォーマットなら大丈夫ぽい
4. bower registerする
さいごに登録
$ bower search XXXXX //一応既に同名のパッケージがないか確認 $ bower register <my-package-name> <git-endpoint>
bower infoで確認しておしまいおしまい
そういえば
javascriptで日付を扱う際の近頃のトレンドは Moment.JS らしい
SourceMapを用いてOSSのライセンス表記を外に出す方法
OSSライセンスの明記をSourceMapで回避しちゃおうという話。
例えば
複数のjsファイルを結合したプロダクトのコードをminifyしてCDNとかに置くパターンを想定。 こういう場合おそらく以下のようなケースがあると思うんですよ。
- 各jsファイルのminifyされたものを結合する
- 全てのjsファイルを結合した後の1ファイルをminifyする
前者のケースの場合は、各min.jsにライセンスが表記してある必要がありますし(まあ大体のOSSライブラリはこうなってるかもだけど)、後者のケースでも結局minifyする時にライセンス表記を消さないようにしないといけないんですね。 自分のチームじゃないんですが、前者の方法でライセンスを保持してるチームも実際にあります。
で自分のとこはどうしようかなあと思っていたところ、チームの先輩がタイトルにあるSourceMapを用いたライセンスの表記の方法を提案してくれました。 SourceMapを用いると以下の2点のメリットを得ることができます。
- minify後の最終ファイルにライセンス文を含めなくて良い
- デバッグしやすい
minify後の最終ファイルにライセンス文を含めなくて良い
らしい。としか言いようがないのですがw このあと触れますがブラウザでminify前のjsを参照できてそっちにライセンスが表記してあれば大丈夫「らしい」です。(ご自身で調べてみることをおすすめしますmm) 自分も調べてみたのですが、残念ながらそれっぽい情報には辿り着けませんでした。 でもpixivでも同じ方法が取られている「らしい」です。 ファイルの大きさ的にはライセンス文の分だけ少なくなるくらいであまりここにはメリット無いかなと、あくまで自分のチームのプロダクトではそう感じました。
デバッグしやすい
これはSourceMapを利用したことがある人なら分かることですが、ブラウザでデバッグがしやすくなります。 具体的にはminify前後のファイル(sample.js/sample.min.js)とSourceMap(sample.min.js.map)を同じパスにデプロイした状態でブラウザでminify済みファイル(sample.min.js)を読み込みます。すると読み込んでるのはminify済みのファイルだけなのに、ブラウザのデバッグツールではminify前のファイル(sample.js)も参照することができるようになります。そのminify前のファイルにブレイクポイント差し込むこともできちゃいます。すばらしいですね。
方法
ほぼほぼ前の項目で書いてしまったんですが一応まとめとして。
- minifyする時にSourceMapを書き出すようにする
- minify後の最終ファイルと同じパスにminify前のファイルとSourMapもデプロイする
僕のチームでは結合&minifyはGruntで自動化してるのでこれ使ってSouceMapを書き出してデプロイとかしてます。
お願い
今回記載した内容についてもし間違いが含まれてましたら、コメント等でご指摘いただけたら幸いです。
追記
1日空けて考えてみたらちょっと思うところがあったので追記。 たぶんSourceMapの良い点としてminifyしてもコードを「可読性の良い状態で」公開することができることなのかと思いました。思い返せば、今回のアドバイスをくれた先輩も常日頃コードは公開すべきみたいな立場にいたなぁ、、、とw
なので今回の内容の本質はライセンスどうこうではなく、コードを公開するという点だったのかもしれませぬ。
さらなる追記
t-wadaさんからこのようなご指摘を受けまして、、、まさに言いたかったことをおっしゃってくれましたw ということでタイトルが誤解を招くような感じを受けるので修正しました。
t-wadaさんありがとうございますー。
FastClick適用下でのチェックボックスにおける問題
業務でデザイナーから受け取ったツールキットを取り込んだ際に、チェックボックスが期待通りに動かないからもしかしてFastClickのせいかなと思って調べたまとめ。
そもそもFastClickとは
スマホではダブルタップを判定するためにタップしてからイベント発火するまで300msの待ちがある。 FastClickはその待ちを消してタップが早く反応するようにするもの。
問題
例として以下のようなHTMLがあるとする。
<label> <div> <input type="checkbox"> </div> </label>
上のHTMLはのエリアをタップするとチェックボックスが反応することが期待されてる。 しかしiOSにおいて期待通りの動きにならず、以下の対応もしてみたがiOS5/6/7のうち6/7は改善が見られなかった。
もしかしたらと思い、FastClick.jsを使わないようにしてみたらiOSでも動いたのでFastClickを調べてみた。
原因
FastClick本家にもissueが上がっていた。 このissueの内容を見てみると以下のような場合において正常に動作するよう対応がされたぽい。
<label><input type="checkbox">Checkbox</label>
もしくは
<label for="checkbox1">Checkbox</label> <input type="checkbox" id="checkbox1">
でもこの対応だとlabelタグの外側にinputタグを配置するか、labelタグの中に含める場合でも 1階層下 までにinputタグがないといけないらしく、前述したHTMLのようにlabelタグから深い階層にinputタグがあるとうまく動かなかった。
で新たなissueが作られてて
この中では 「深い階層でもいけるようにできるか調べてみるぜ!」 的なことが当初言われてたあとに、 「cssいじったら動いたしこっちのがコスト低いからcssいじってちょ!」 っていう結論に至ってました。 でどうcssをいじればいいかというと
label > * { pointer-events: none; }
ためしに上記のcssを前述のHTMLを使ってる画面に反映させてみたところ、見事に動きました。
結論
- FastClickを使うのをやめる (一番簡単だけどできればしたくない)
- cssに上述のやつを追加する
- inputタグがlabelタグの 2階層 以上深くならないようにHTMLを変更する
DateFormat.js
Javascriptで日付を整形するのめんどくさい人向けにライブラリ作りました。(俺得)
例えば “1989年8月25日” みたいな日付を取得するためには以下のような感じになるかと思います。
var d = new Date('1989/8/25'); var year = d.getFullYear(); var month = d.getMonth() + 1; var date = d.getDate(); console.log(year + '年' + month + '月' + date + '日');
いちいち変数に入れて無駄に行数増やしてる感はありますが、getMonth()で+1するのとかよく忘れます、はい。
上の例は1989/8/25固定でやってるから微妙ですが、例えばサーバから取ってきたタイムスタンプを特定のフォーマットにして表示したい時とか。
ということで今回このような日付のフォーマットを簡単にできるライブラリを作ってみました。
PHPのdate()みたいにフランクにフォーマットできるのを目指してます。
完全独立なグローバル関数でいいかなーとか、jQueryのプラグインにしてみようかなーとか最初は思ったんですが、
JavaScriptで日付使う時はまあDateオブジェクトだし、Dateオブジェクトのprototpyeにぶっこんじゃえーって軽いノリで実装しました。
こんな感じで使えます。
var d = new Date(1388599445000); d.format('Y-m-d H:i:s'); //=> "2014-01-02 03:04:05" d.format('n/j h:i') //=> "1/2 3:04"
これを使った場合、最初の例は下のような感じで書けるようになります。
var d = new Date('1989/8/25'); d.format('Y年m月d日');
やってることはものすごい簡単で特定の文字を正規表現でreplaceしてるだけですね。
githubにこういうの公開するの初めてなのでライセンスとか書くのちょっと恥ずかしかったですw
MITって書いとけばいいかなみたいなw
あとテストも書いたんで、travisとか使ってみました。
初めて使ってみたのですが、無料であそこまでできるのすごいすね。
ライブラリ自体は超たいしたことしてないし、他に同じようなの作ってる人たくさんいるだろうし、むしろこういう初めてやることができて良かったです。
と、今回は経験として誰得なライブラリを作ってしまいましたが、いづれは誰かの役に立つようなモノも作れたらいいですねー
[追記]
がっつりおんなじ思想で作られたライブラリを後日ふと見つけましたw
すばらしいです。拝承。