整理したものはTech sheetsにあるよ

2016年12月12日月曜日

TypeScript 2.1.4でXMLHttpRequestのコールバックがコンパイルエラー

みんなー!

TypeScriptで、次のコードをコンパイルしてみよう!


var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://tech.mokelab.com', true);
xhr.onload = function() {
    console.log(this.response);
};
xhr.send();

onloadプロパティにセットするコールバック関数は、呼ばれる時、thisにXMLHttpRequestがセットされる。だから、アローではなくfunctionを使うんだったね!

でもこのコード、TypeScript 2.1.4でコンパイルすると次のようなエラーがでちゃう。


moke.ts(4,22): error TS2339: Property 'response' does not exist on type 'XMLHttpRequestEventTarget'.

responseっていうプロパティはXMLHttpRequestにあるはずなんだけど、なぜか型がXMLHttpRequestEventTargetになってて、「そんなプロパティは知らないよ」というコンパイルエラーが出てるね。

この動きを理解するために、TypeScript 2で導入された構文を確認しよう!TypeScript 2で、コールバック関数をセットするプロパティに、thisの型が指定できるようになったんだ。こんな感じで定義を書くよ。


interface Moke {
    onclick: (this: MyObject, ev: Event) => any;
}

第一引数名がthisで、型を「実際に呼ばれる時にthisにセットされている型」にすると、function()の中でも適切に型チェックしてくれる!

そして本題のXMLHttpRequestのコールバック部分。2.1.3までは次のようになっているよ(一部のみ抜粋)。

interface XMLHttpRequestEventTarget {
    // onloadのみ掲載
    onload: (this: this, ev: Event) => any;
}

これが、TypeScript 2.1.4だとこうなってた!

interface XMLHttpRequestEventTarget {
    // onloadのみ掲載
    onload: (this: XMLHttpRequestEventTarget, ev: Event) => any;
}

もちろん実際に呼ばれるときは、XMLHttpRequestEventTargetを実装しているXMLHttpRequestがthisにセットされるよ。 なので、いまひとつだけど次のようにキャストするしかないみたい。。。

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://tech.mokelab.com', true);
xhr.onload = function() {
    console.log((<XMLHttpRequest>this).response);
};
xhr.send();

同じ問題でハマった人の助けになるといいなー

2016年10月18日火曜日

[Golang]Goの型付き比較をみてみよう

みんなー!モケラだよ

今日は、Go言語の型のおはなしだよ。

Goは、こんな風に知ってる型の別名を作れるよね!


type Moke int

このMokeという型はintの別名で、キャストするには次のように書くんだったね!

val :=  Moke(1)
val2 := int(val)

じゃあ、意味としては同じ1を次のようなmapにいれると、どうなるかな?

type Moke int
type Piyo int

func main() {
        hash := map[interface{}]string{
                Moke(1): "Moke",
                Piyo(1): "Piyo",
        }
        fmt.Printf("1 = %s\n", hash[1])
        fmt.Printf("Moke(1) = %s\n", hash[Moke(1)])
        fmt.Printf("Piyo(1) = %s\n", hash[Piyo(1)])
}

1が3個mapに入ってるね。実行結果はこちら!
$ go run main.go
1 =
Moke(1) = Moke
Piyo(1) = Piyo

比較は、型も考慮してやってくれるのが、Go言語!

2016年6月4日土曜日

[Android]FCM(Firebase Cloud Messaging)をいれようとして大ハマりしたおはなし

みんな、こんにちはー!

Google I/O 2016で、AndroidへのPush通知の仕組みがGCMからFCM(Firebase Cloud Messaging)に移行されるという発表があったね!

そこでモケラボも、既存のアプリを1つ、GCMからFCMに移行してみたよ!

とっても簡単かなと思ってたけど、世の中そんなに甘くないね! ちゃんとPushが届くのを確認するまでに1人日(1モケラ日?)かかっちゃった。

AndroidアプリへのFCMの導入は簡単。GCMの時よりもっと簡単になってたよ!これからおはなしする問題に遭遇しなければね!

詳細はTech sheets でまとめるので、ここでは概要だけ紹介するね。

FCMを導入しよう

FirebaseのWebコンソールでプロジェクトを作る

検索するとたくさんでてくると思うので、ここは省略するね。

FirebaseのWebコンソールで、Androidアプリを登録する

アプリのパッケージ名をいれよう。SHA-1は空っぽでもだいじょうぶ
アプリを追加すると、勝手にgoogle-services.jsonのダウンロードが始まるよ。

google-services.jsonをapp配下に配置する

これは、ちょっと前のGCMと同じだね!ちなみにProductFlavorを使ってる場合は、app/src/Flavor1/google-services.json のように、app直下じゃなくてFlavorの配下に置こう。

ルートのbuild.gradleを編集する

dependenciesのところを次のようにするよ。
dependencies {
    classpath 'com.android.tools.build:gradle:2.1.0'
    classpath 'com.google.gms:google-services:3.0.0'
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
}

appのbuild.gradleを編集する

dependenciesに
compile 'com.google.firebase:firebase-messaging:9.0.1'
を追加しよう。そしてファイルの一番最後の行
apply plugin: 'com.google.gms.google-services'
を追加しよう。

これだけ!

実はこれだけでOKなんだ。初期化のプログラムを書く必要すらなくなってる!サンプルを動かすとちゃんとPushとどいたよ!

でも既存のアプリにいれたら動作しなかった!

ここからが本題。既存のアプリにいれたらなぜか動かなかったんだ。モケラボでいろいろ原因調査したので、その過程もセットでどうぞ!

トークンを取ろうとすると例外を投げる

FCMのトークンは
String token = FirebaseInstanceId.getInstance().getToken();
で取得できるよ。でもうまく動いていないときは、このメソッドが例外を投げるんだ。
java.lang.IllegalStateException: FirebaseApp with name [DEFAULT] doesn't exist.

このFirebaseAppというのがキーワードみたいだね。ということで次にFirebaseAppについて調査してみたんだ。すると。。

FirebaseAppが1つもみつからない

FirebaseAppというオブジェクトは、
List<FirebaseApp> apps = FirebaseApp.getApps(this); // thisはContext
で取得できるよ。ちゃんと動作していれば1つ以上返ってくるよ。

でも動かなかった時はこのメソッドが長さ0のリストを返していたんだ。さっきの例外はこれが原因だったのかな?

小さいプロジェクトだと1つ返ってきて、ある程度の規模のプロジェクトでなぜか1つもとれないのはなぜだろう。と、その前にこのFirebaseAppはどこで生成されてるんだろう?

Applicationクラスをこっそり差し替えたりするのかな?と思って、あえて独自のApplicationクラスをいれてみたけど、結果は同じ。ここではなさそう。

ログを眺めてみた

次に、うまくFirebaseAppが取得できた時と、失敗してる時のログをぼんやり眺めてみたよ。すると、うまくいっている時にはこんなのが見つかったよ!

06-04 07:10:09.180 9933-9933/com.mokelab.fbdemo I/FirebaseInitProvider: FirebaseApp initialization successful

「FirebaseAppの初期化に成功したよ!」というログだね。失敗してる時にはこれ無かったんだ。と、ここに新キャラの「FirebaseInitProvider」というのがいるね。Providerって名前がついてるから、きっとAndroidManifest.xmlにいるよね。

AndroidManifest.xmlを見てみた

サンプルではAndroidManifest.xmlを全く編集していないので、きっとビルド時に追加されてると思ってbuild/intermidates/manifests/の下に生成されているAndroidManifest.xmlを見てみたよ。

 <provider
   android:name="com.google.firebase.provider.FirebaseInitProvider"
   android:authorities="com.mokelab.fbdemo.firebaseinitprovider"
   android:exported="false"
   android:initOrder="100" />

やっぱりいたね!ということは、なぜかマージされないAndroidManifestが今回の原因だったみたい。マージされないのなら自分で書けばいいや!ということで、アプリ本体のAndroidManifest.xmlに次の項目を追加したらちゃんと動くようになったよ!一部アプリのパッケージ名があるのでそのままコピペして動きません!という苦情コメントは受け付けないよ!

<receiver
    android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
    android:exported="true"
    android:permission="com.google.android.c2dm.permission.SEND" >
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

        <category android:name="com.mokelab.fbdemo" />
    </intent-filter>
</receiver>

<!--
     Internal (not exported) receiver used by the app to start its own exported services
     without risk of being spoofed.
-->

<receiver
    android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
    android:exported="false" />

<!--
     FirebaseInstanceIdService performs security checks at runtime,
     no need for explicit permissions despite exported="true"
-->

<service
    android:name="com.google.firebase.iid.FirebaseInstanceIdService"
    android:exported="true" >
    <intent-filter android:priority="-500" >
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
    </intent-filter>
</service>

<provider
    android:name="com.google.firebase.provider.FirebaseInitProvider"
    android:authorities="com.mokelab.fbdemo.firebaseinitprovider"
    android:exported="false"
    android:initOrder="100" />

残る謎

今回解決できていないのは、どのような条件の時に、ライブラリプロジェクトのAndroidManifestの項目がマージされないかということ。だれか詳しい人、教えて!

2016年5月24日火曜日

Google I/O 2016 参加レポート!

みんな、こんにちはー!

今年はだいひょーが3年ぶりにチケット手に入れたので、Google I/O 2016@Mountain Viewに参加してきたよー!

各セッションについては、後で個別の記事として公開していくよ!

では、いってみよー

0日目

例年、Google I/Oは開催の前日から参加バッヂの受け取りができるよ。なので0日目って書くね。

(去年も同じだったかもだけど)今年は、「バッヂの受け取り順でKeynoteのだいたいの席が決まるよ!」という仕組みだったよ。バッヂ受け取り開始はなんとAM7時。最初はすごーい行列ができてたみたいだけど、AM8時の時点では列はできてなくてラッキー
Registrationの時点でいろいろ、モノがもらえたよ!

  • Tシャツ
  • Cardboard
  • 水筒
  • サングラス
  • バンダナ
  • 日焼け止め
FESTIVAL SURVIVAL KITって書いてるくらいだから、過酷な環境であることは間違いないね><


いろんなグッズが買えるI/Oストアも0日目は空いてなかったので、片道CalTrainで1時間かけてRegistration3分、帰りもCalTrainで1時間 で0日目はおしまい!

1日目

Google I/Oの1日目はKeynote(基調講演)からスタート!Google I/O期間中はGoogleから朝食/昼食/夜も少しでるよ。だいひょーによると、3年前は「朝食をとるか、Keynoteの列に並ぶか」みたいな選択だったみたい。今年はRegistrationの時点でだいたいの席が決まってるので、頑張って並ぶ必要はなかったけど、割と前のほうで並んでたよ!

座ったのはこのあたり。最初は日が当たらなかったのでよかったよ。そう、最初は。。。


今年は屋外の会場だったので、なんと後方には芝生席!

この写真からもわかるように、太陽が照りつける席だったみたい><

Keynoteの細かい内容は大手メディアさんのほうが写真付きで詳しく伝えていると思うので、ここではモケラボが感じたことをざっくりと伝えるね。まだ、実際に動くものは展示とかなかったけど、Google assistantをこれからどんどん推していくのかなーと感じたよ。話の流れとか、今の状況(Context)をふまえて、こちらの質問に答えてくれたり、提案してくれたりする時代がすぐやってくるかもねー!


ちなみにkeynoteの最中も太陽は移動するので、始まって1時間45分あたりでボクのいる席も日光が直撃するようになってたよ。Mountain Viewの日差しはとっても強いので、5分ぐらいでだいひょーのMacのキーボードが触れないくらいに熱くなってたよ。ボクはこんな感じに。。。


Keynoteが終わった後はランチタイムのあとに1時間ずつのセッション開始!セッション会場は駐車場に建てられた小屋やテントの中で、中はちゃんと冷房が用意されてたよ!だいひょーはどのセッションに行くか予定をたててたみたいだけど、会場(ステージ)によっては100人ぐらいしか入らないとこもあって、「並んだけど入れなかった」という時間帯が続出><


初日のセッション終了後は、軽食のあとにコンサート。サンフランシスコに泊まってたので、体力温存ということで1日目は早めにホテルに戻ったよ。

ここまでのTweetまとめはここ! http://togetter.com/li/977062

2日目

1日目の最初に書き忘れてたけど、今回はMountain Viewというとこで開催だったので、サンフランシスコやサンノゼから会場までのシャトルバスが出てたよ!(Googleの社員用バスではなく、単なるツアー用のバスだったみたい。ざんねん。。。)泊まってたホテルからは歩いて10分ぐらいのところがI/O用のバス乗り場だったのはよかったんだけど、出発が朝6時台という過酷なスケジュール!

2日目はKeynoteはなく、朝9時からセッション開始!デザインや、Android Studio 2.2の新機能の話など盛りだくさん!詳細は別の記事でまとめるね!

お昼に日本人大集合!写真を再掲載していいかはちょっと確認とれてないので、Google+の投稿をみてね!(ついでにモケラボのだいひょーも探してみよう)

https://plus.google.com/u/0/104222542046253499086/posts/PMQy17TrdtL

午後もセッションたくさん!Android Wear 2.0のセッションにふらっと入ってみたけど、Android Wearもかなり面白いことになりそう!

午後最後のセッション(?)のSpeechless!
出題者が事前に5ページのスライドを用意し、チャレンジャーが指定されたテーマに沿っていきなりスピーチをするという無茶振り企画!

どんなことになるかというと、「Googleの(架空)新プロダクトについてプレゼンしてね!」というお題が出されて、こんなスライドがでてくる!

この面白さは、きっと会場でないと、伝わらない!

夜はAfter hoursというパーティ!屋外だったのですごいお祭りな感じだったよ!


セッション会場の1つがプラネタリウムになって、中でバンド演奏があったり!

Project TangoのARゲーム(拡張現実)もあったよ!

Mountain Viewの気候は夜はかなり冷えたよ(Googleが毛布配ってた)

3日目

あっという間に最終日!最終日はセッションの数も少なめで、実は全セッションはYouTubeで見れるのでGoogleストアまで自転車借りて行ってみたよ!(無料で自転車貸し出しやってたよ)

Googleストアの近くに、歴代のAndroidバージョンのモニュメントがあったよ!昔はもっと目立つ位置にあったみたい。一部を紹介!

なかったことになってたHoneycombも!


離れた場所に今のところ最新のマシュマロもいたので、だいひょーが記念撮影してたよ

そんなこんなであっという間の3+1日間。全部のセッションYouTubeで見れるからいいや!という方に、「現地で参加するともっとたのしい!」が伝わるといいなー!



(チケットを手にいれるのが年々難しくなってるけどね。。。)





2016年3月22日火曜日

[Android]Jackを有効にしているとButterKnifeがうまく動かない

こんにちは!

Android N Previewの登場とともに、Jackも登場したねー!JackとはJava Android Compiler Kitのことで、Javaのソースとかをdexにするまでのツールチェーンなんだ。詳しいことはここを読んでね!

Jackを使ってみるには、次の4つが必要!

  • Android Studio 2.1 preview版を使っている
  • gradle pluginは2.1.0-alpha3
  • buildToolsVersionは"24.0.0 rc2"
  • build.gradleに設定をいれる

build.gradleには、こんな風に「Jack使うよー」と、「Java 8を使うよー」と記述するよ。そう、Jackを有効にするとJava 8の構文とかが使えるようになるんだ。ラムダ式も使えるようになるので、コールバックの部分が多少読みやすくなるよね!

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "24.0.0 rc2"

    defaultConfig {
        applicationId "com.mokelab.demoapp"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        jackOptions {
            enabled true
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

いいことばかりのJackさんなんだけど、開発中なのか、困った動きをしてるんだ。。。

それが、今回のタイトルであるButterKnifeがうまく動かないという問題。ButterKnife.bind()を呼んでも実行時にちゃんとバインドされず、@OnClick()をつけたメソッドが呼ばれなかったりするんだ。実行時にようやくわかるのでタチが悪いよね!

何が起きてるかをちょっとだけ調べてみたよ。

  • aptでViewBinderクラスは生成されてるみたい
  • apkをばらして.classを確認すると、生成されたはずのViewBinderクラスはいない
もちろん、jackOptionsのenabledをfalseにするとapkにちゃんとクラスは含まれてて、思った通りの動きをするよ。

Jackの問題だと思うので、改善されるのを待つばかり。。。


2016年2月7日日曜日

[Android]IabHelperは中断に対応できていない!

みんな、こんにちはー!

Androidでアプリ内課金(In-app billing)を実装するとき、Android SDKのサンプルとしてついてくるIabHelperをそのまま使ってる人、たぶん、いるよね!

購入時はこんなコードを書いているんじゃないかな?



private IabHelper mHelper;

void buyMokeMoney() {
    mHelper.launchPurchaseFlow(this, "product_money", REQUEST_PURCHASE, new IabHelper.OnIabPurchaseFinishedListener() {
        @Override
        public void onIabPurchaseFinished(IabResult result, Purchase info) {
            if (result.isSuccess()) {
                // upload receipt JSON to app server.
                sendReceipt(info);
            }
        }
    });
}

「購入処理が終わったら、レシート情報をサーバーに送信する」という処理だね。メソッド1つで購入の手続きができるのは、便利だよね。

でも、このIabHelperのlaunchPurchaseFlow()には、1つ構造的な欠陥があるんだ。

アプリにこのような実装をいれて、次のようなクレームがきたことはないかな?

「購入したのに仮想通貨が増えていません!返金してください!」

コードを見る限り、購入手続きが終わったらonIabPurchaseFinished()が呼ばれて、購入処理が成功していればアップロード処理が行われるはずだよね。

「おそらく購入後の通信に失敗していると思うので、アプリを再度起動してください。」みたいな回答をしてたりするんじゃないかな?

でもよく考えてみよう。購入処理で通信できていて、アプリのサーバーとの通信が失敗するって、なかなかレアケースじゃない?みんながすごい勢いで購入処理をやって、アプリのサーバーが処理できないことはあるかもだけど、ちょっと考えにくいよね。

なぜサーバーとの通信が失敗(したように見える)するんだろう?原因はIabHelperのlaunchPurchaseFlow()を使っているからなんだ。

launchPurchaseFlow()では、引数のActivityを使って購入処理用のIntentを発行しているんだ。ということは、購入処理中に電話かかってきたりして、アプリが中断状態になることがあるよね。

IabHelperのインスタンスは、初期化の手続きとかがあるのでActivityのフィールドにいれるよね。

あれ、中断時にこのIabHelperのインスタンス、保存してる?
というか、誰も保存していないよね!IabHelper、Parcelable/Serializableではないから。

これが、時々購入後の処理が実行されない原因なんだ。IabHelperは、構造的に中断に対応できていないんだ。

どうすればいいの? という答えは、次回!