Daggerを使ってSingletonにする仕組み

ものすごいあほうなことを書いているかもしれませんが、そのときはご指摘ください。

Daggerを使って依存性を注入する際に、アプリ内でSingletonになるようにすることあるじゃないですか。

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    void inject(MainActivity activity);
}

@Module
public class AppModule {
    private Context context;
    public AppModule(Context context) {
        this.context = context;
    }

    @Provides
    @Singleton
    public SomeClass provideSomeClass() {
        return new SomeClass().initializeWithDefault();
    }
}

みたいに、SomeClassがアプリ内でシングルトンになるようにすると。

今までずっと、@Singletonって指定してるから実現できてるんだと思っておりました。実際には違います。これはそもそもAppComponent自体がアプリ内でシングルトンになっていなければ実現されません。

このAppComponentはApplicationクラスを拡張して、そこで初期化してるから@Singletonという指定が効くのです。このAppComponentを、ActivityのonCreateで初期化していたらシングルトンにはなりません。AppComponentインスタンスの中ではSomeClassのインスタンスは一度生成されたら使いまわされますが、AppComponentのインスタンスが複数生まれてしまえば生成されるSomeClassもAppComponentのインスタンスの数と同じだけ増えていくことになります。

そして@Singletonは別に@Singletonでなくてもいいのです。自分でスコープを作って、例えば

@AppScope
@Component(modules = AppModule.class)
public interface AppComponent {
}

@Module
public class AppModule {
    @Provides
    @AppScope
    public SomeClass provideSomeClass() {
    }
}

としても結果は同じです。Componentにつけたスコープ名の中でインスタンスを使いまわすっていう感じになるわけです。

だから@Singletonつけてるからシングルトンになるわけではないのです。AppComponentのインスタンスがアプリ内で1つだからこそ、シングルトンにできているわけです。

ここがあやふやなままだったので、Daggerよく分からん状態だったのですが、これで一歩前進できます。

カスタムViewが想定通りに描画されているかテストする

カスタムViewを作って、しかもそれがCanvasを使って描画するようなものだった場合、どうやって動作確認をしていますか?

私はこれまで実機で動かして、目視で確認していました。Viewの見た目なので目視で確認するしかないんですけどね。それを手動でやっていました。

しかしつい先日、手動での確認が難しい案件に出くわしました。それは端末のセンサーの値を読み取って、その値にあわせてカスタムViewの描画が変わるようなものでした。これは手動で確認したくとも難しいです。

例えば心拍数を元に描画が変わるカスタムViewを想像してみてください。心拍数が120を超えたら特殊な表示を行う仕様だと思ってください。実機でそれを確認しようと思ったら、心拍数を上げるべく毎回運動しなきゃいけない、なんてことになるわけです。

そういったViewの描画、見た目の確認がしたい。こういうの、みんなどうやってテストしているのだろう。それが今回の出発点です。

サンプルプロジェクトをGitHubに置いてみたので良かったら見てみてください。というよりコードの解説はこの記事では一切ありませんので、GitHubでみてください。

やり方書かないのもあれなので、追記しました。

サンプルについて

TextViewの周りを線でデコレーションするカスタムViewがテスト対象です。どこを描画するかを指定してinvalidate()すると、TextViewの周りに線が描画されます。onDrawメソッドをオーバーライドして、Canvasを使って線を描いています。

今回はこの描画がちゃんとできるかを確認する、というそんなテストです。

スクリーンショットを撮って確認しよう

Viewの描画を確認したいわけですから、ユニットテストでは確認できません。

そこでまず思いついたのが、スクリーンショットを撮って、その画像で確認できたらいいんじゃないかというものでした。以前にEspresso+Spoonで自動的にスクリーンショットを撮るテストの話を見たのを覚えていたので、これを使えばいけそうと考えました。

問題が2つ

しかしSpoonを使ってスクショを撮るには、WRITE_EXTERNAL_STORAGEパーミッションが必要になります。プロダクト側で必要なら問題ありませんが、そうでない場合はテストのためだけに不要なパーミッションを追加することになります。できればそれは避けたい。

また、スクショはActivityを起動してそれを撮影することになるわけですが、実際に対象のViewを表示するActivityがテストに適した作りになっているとは限りません。

例えばこのサンプルプロジェクトでも、MainActivityを使ってテストできなくもありません。Espressoを使ってボタンを押すようにすれば、カスタムViewの描画は切り替わります。しかしこのMainActivityの仕様だと、カスタムViewの上と下に線を描画した状態をテストできません。

つまり、実際に使うActivityとは別にテストのためだけのActivityが欲しいわけです。

ではそんなActivityをプロダクションに混ぜるのかという話になりますが、それも避けたい。

テスト用のProduct Flavorsを用意する

そこでテスト用のプロダクトフレーバーを作成することでこれを回避しました。これもあまりスマートなやり方ではなく、できれば避けたかったのですが仕方ありません。

debugビルドにだけテスト用のパーミッション、Activityを含めるという方法もなくはないのですが、プロダクトフレーバーで切り分けてしまったほうが潔いかなと思ったのです。

テスト用のAndroidManifestとActivityさえ用意できれば、後は簡単です。

余談、androidTestに専用Activityを作ればいいんじゃないかという考え

ちなみに私は最初、androidTest配下にテスト用のActivityを追加して、それ経由でテストすればいいんじゃないかと考えました。しかしそれはうまくいきません。

なぜなら、androidTestに配置したコードはテスト用のAPKにコンパイルされるからです。

私は今までずっと勘違いしていました。androidTestに書いたテストを実行したら、mainに配置してるテスト対象コードにテストコードを追加したAPKが作成されて、それでテストが実行されてるんだと思ってました。どうもそうではなくて、普通のAPKを単にテスト用APKで外部から操作してただけなんですね。

https://stackoverflow.com/questions/27826935/android-test-only-permissions-with-gradle

作り方

まずproductFlavorを追加します。サンプルでは普段使うやつをDefault、Viewのテスト用のものをUiTestとしました。ここではUiTestを追加するとして書いていますので、適宜読み替えてください。

Read full post gblog_arrow_right

リアルタイムで心拍数を計測できるHeart Rate Monitor Wearをリリースしました

リアルタイムで心拍数を計測・表示するAndroid Wearアプリをリリースしました。

特徴はリアルタイムで心拍数が分かること、Wear端末で計測した心拍数がそのままスマホに表示可能な機能があることです。

アプリはGoogle Playで公開中です。

開発の動機

運動不足解消とダイエットのために、室内でフィットネスバイクを漕いでいたのですが、あまり効果が現れませんでした。そんな中、ダイエット目的の運動は心拍数を元に運動すると効率がいいという情報を目にしました。

心拍数がだいたい40%〜60%くらいになるように運動すると、脂肪燃焼の効率がいいらしいです。

そこでAndroid Wearを使って心拍数を確認しながら運動することを考え、開発を開始しました。

しかし運動中にいちいち腕時計の画面を確認しないといけないのはちょっと面倒です。特に私はスマホでAbemaTVを見ながら運動していました。心拍数を確認するのに手元に視線を動かすことは、運動から気がそれるだけでなく、番組からも目を離すことになってしまいます。

どうせスマホの画面を見ているのだから、画面の右隅にでも計測中の心拍が表示されればいいのに。そんな思いからこのアプリが誕生しました。

スマホへのオーバーレイ表示

こんな感じで画面の右上に半透明で心拍数が表示されます。

オーバーレイ表示

YouTubeなどで動画を見ながらでも心拍数を確認できます。今はやりのポケモンGOをやりながらでも確認できます。(ただポケモンGOだと心拍数をコントロールするような運動はしないでしょうけど)

心拍の推移を記録

このアプリは心拍数をリアルタイムで表示するだけでなく、その推移を記録します。これはWear端末のみで運動した場合でも記録できます。

例えばWear端末のみを装着してジョギングを行う→家に帰ってスマホでジョギング中の心拍数の変化を振り返る、といった使い方ができます。

Saved heart rate ja

使い方

Android Wearのアプリ一覧からこのアプリを起動すれば心拍数の計測が始まります。

スマホ側にも起動ボタンを用意しているので、そちらからも起動できます。

起動・終了ともに、スマホとWearが通信可能な状態なら、スマホ側で起動すればWear側も起動します。終了に関しても同様です。

計測中はWear端末の画面上に心拍数が表示されます。アンビエントモードに対応しているので、バッテリーに配慮した作りになっています。

スマホに心拍数を表示するよう権限を許可していれば、心拍計測が始まれば自動的にスマホ側にも心拍数が表示されるようになっています

計測を終了するには、Wear端末上のアプリ画面で右に向かってスワイプします。スマホで計測終了ボタンを押してもOKです。

計測したデータはログとしてスマホで後から確認することができます。ただし、あまりにも長い時間計測をした場合、データがうまく保存できない可能性があります。2〜3時間は大丈夫だと思いますが、端末の性能やセンサーの精度などにも影響されるので一概には言えません。

必要な権限について

  • ボディセンサー
  • 他のアプリに重ねて表示
  • 端末スリープの無効化
  • ネットワークアクセス関連

最初2つに関しては、お使いの端末がAndroid 6.0以上の場合には、実行時に許可するかどうかを選択できます。

Read full post gblog_arrow_right