読者です 読者をやめる 読者になる 読者になる

ほげほげ(仮)

仮死状態

RxJavaのHotObservableのテストコード

HotObservableのテストコードを書く方法です。ちょっと調べたのですが、良い方法が見つからず自分なりに考えてみました。

もっと良い方法あったら教えてください。

環境

ごめんなさい、RxJava1です。RxJava2まだ追えてません。

対象コード

単純な足し算をして、それをHotObservableで通知するだけのものです。

これのテストコードを書きます。

public class Hoge {

    private final PublishSubject<Integer> subject = PublishSubject.create();
    public final Observable<Integer> observable = subject.asObservable();

    public void operation(int a, int b) {
        subject.onNext(a + b);
    }
}

TestSubscriber

RxJava関連のテストコードでよく使われると思いますが、TestSubscriberというのがあります。

これにawaitTerminalEventっていうメソッドがあって、onCompletedonErrorが呼ばれるまで待ちます。普通のColdなObservableとかはこれを使って処理が終わるのを待つことが可能ですが、HotなObservableの場合はonCompletedonErrorも呼ばれないので処理を待つことが難しいです。

awaitTerminalEventタイムアウトが設定できるので、とりあえずそれでお茶を濁すことも可能ですが、常にその時間待つことになります。

// 1秒でタイムアウト
// HotObservableの場合は毎回1秒待つ
testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);

なので、onNextが呼ばれたときにonCompletedを呼ぶように継承してHotObservable用のTestSubscriberを作ります。

ついでに呼ばれる回数も指定できるようにしてあります。

public class HotTestSubscriber<T> extends TestSubscriber<T> {

    private int count;

    public HotTestSubscriber(int count) {
        this.count = count;
    }

    @Override
    public void onNext(T t) {
        super.onNext(t);

        count--;

        // 指定回数呼ばれたらonCompletedを呼んで終了
        if (count == 0) {
            onCompleted();
        }
    }

    public static <T> HotTestSubscriber<T> create() {
        return new HotTestSubscriber<T>(1);
    }

    public static <T> HotTestSubscriber<T> create(int count) {
        return new HotTestSubscriber<T>(count);
    }
}

テストコード

先程の足し算のテストコードを書いてみます。

public class HogeTest {

    @Test
    public void operation() throws Exception {
        Hoge hoge = new Hoge();

        HotTestSubscriber<Integer> testSubscriber = HotTestSubscriber.create();
        hoge.observable.subscribe(testSubscriber);

        hoge.operation(1, 2);

        // 処理が終わるのを待つ(一応タイムアウトも設定しておく)
        testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);

        // onNextが呼ばれた回数を確認
        testSubscriber.assertValueCount(1);

        // onNextに入ってきた値を確認
        testSubscriber.assertValue(3);
    }
}

これでHotObservableもテストできるようになりました。

ObjectBox所感

greenrobotが新しいmobile object databaseのObjectBoxのBetaをリリースしたようです。

ObjectBox - The new Mobile Database (Beta) - Open Source by greenrobot

greenrobotは、EventBusgreenDAOを作ったとこです。

AndroidにおいてはRealmと競合になりそうな感じですかね。

機能

アナウンスの記事を見ると5つの機能をアピールしてますね。

  • Superfast
  • Object API
  • Instant unit testing
  • Simple threading
  • No manual schema migrations

ちょっと試してみた

ちょっと試して、Realmとの比較してみました。

セットアップは省略。

Entity

次のようなPOJOクラスを設定してアノテーションを指定します。とりあえず、書くのはこれだけです。

@Entity
public class Note {

    @Id
    private long id;

    private String text;

    private String comment;

    private Date date;
}

これをビルドすると、次のように勝手に色々生成されます。コンストラクタとgetter/setterですね。

f:id:STAR_ZERO:20170125180545g:plain

これはaptでよくあるbuild/generatedにコードを作るのではなく直接コードが変更されます。ちょっとこれは怖いかも…

あとは、app/objectbox-models/default.jsonが作成されます。スキーマ情報ですかね。

Insert

Insertはすごく簡単でEntityのインスタンスを作ってBoxというオブジェクトに入れるだけです。

Note note = new Note(0, "text", "comment", new Date());
notesBox.put(note);

idを0にするとAutoIncrementで自動で採番してくれます。Realmには無い機能なのでこれは地味に嬉しいかも。

あと、Realmと違ってTransactionで囲む必要が無いですね。

Update

Updateはidを既存のもにすると更新されますね。他はInsertと同じです。

Delete

削除はidを指定するだけです。他にもEntityをそのまま渡しても削除できます。

notesBox.remove(1);

全件削除も簡単です。

notesBox.removeAll();

Realmの場合は一度Queryで取得してから削除になるので、ここも違いますね。

Query

クエリも単純です。

QueryBuilder<Note> builder = notesBox.query();
List<Note> notes = builder.build().find();

条件指定

QueryBuilder<Note> builder = notesBox.query();
Query<Note> query = builder.equal(Note_.id, 2).build();
List<Note> notes = query.find();

このあたりはRealmとそこまで変わらないですが、カラムの指定がRealmは文字列に対して、ObjectBoxは定数みたいなのが使えます。

test

Gradleのほうで、次のように書いてSyncすると勝手にテストの雛形みたいなのが出来ます。

objectbox {
    generateTests true
}

テスト用のsetupとかはありがたいかもしれないですね。

auto migration

Entityに追加したり、削除したりすると自動でマイグレーションしてくれます。これはかなり強力な気がします。

Entityにフィールドを追加してビルドしたら、勝手にまた色々追加されました。逆に削除したらコンストラクタは変更されたけど、getter/setterは残ってしまいました(バグかな?)

既存レコード側の影響としては、追加されたフィールドは初期値が設定されるみたいで、Stringがnullでintなら0でした。このあたりは制御できるのかな?

さすがに型をStringからintのように変更するのはダメでした。

まとめ

個人的な感想として、軽く触ってみた感じ、だいぶ使いやすい印象でした。

一番ネックになるのがEntityクラスがビルド時に書き換わることかなぁと。ただ、ここを頻繁にいじることがなければ許容範囲かなって気もしますが、どうでしょうかね…

Betaだし、ObjectBrowserも無いので(issueはここ)、まだまだ実際に使うことはないですが期待したいです。

Google Play ConsoleにAPKファイルをアップロードするGradle Pluginを作った

タイトルのとおりです。

ファイルを指定してAPKファイルをPlay Consoleにアップロードするだけです。

あと、Proguardのmappingファイルもついでにアップロードできます。

github.com

たぶん認証のJSONのやり方が分からないかなぁとか思ってます。

今のREADMEはfastlaneのドキュメントにリンクしてるだけです。なので、近いうちにちゃんと書きます。

作った経緯

実はすでにGradle Pluginとしては別のものが存在してました。

github.com

これではダメな理由がいくつかありました。

  • ビルドからアップロードまでが1つのTaskで実行される
    • APKファイルをアップロード前に一度確認したい
  • ストア情報も色々アップロードする
    • そこまでやってくれなくて良い
  • 開発止まってる?PR/issueが処理されてない。

で、ぼくが求めるものが次のような感じです

  • APKファイルを指定してアップロードするだけ
  • ストア情報は特に何もしない
  • Proguard mappingファイルはアップロードしたい

っていうことでこれを満たすように作りました。

おそらく今後、これ以上の機能を追加するつもりはないです。たぶん。

とりあえず

完成したので満足です。

2016年に買って良かったもの

Pebble Time Round

つい最近Fitbitに買収されPebbleブランド消滅のお知らせがあって非常に残念です…

本当に気に入っていて、お風呂以外はずっとつけています。歩数や睡眠もそれなりに取れていてかなり便利です。Androidの通知も受け取るようにしてるので、いちいち端末を取り出す必要なく確認できるのも非常に良いです。

来年またPebbleで買おうと思ってたんですけどね…どうしよう…

iPad Pro

iPad Pro - Apple(日本)

前使ってやつがホームボタン効かないし、反応悪いしで買いました。ついでにApplePencilも。

基本的にKindleで本かマンガしか読んでないですけど…

ApplePencilはKindleでマーカみたいなのをつけるときに使ってます。

Withings 体重計

これまでほとんど体重を測っていなかったのですが、歩数とか睡眠は計測し始めたのでついでに体重も計測し始めました。

Android端末と連携させておくだけで、あとは体重計に乗るだけで勝手に記録していきます。IFTTTとも連携できるのも強いです。

MESH Moveタグ

IoTデバイスですね。動きを検知して色々できます。

ぼくはこれを自宅の鍵の監視として使っています。詳しくは前に記事を書いたので、そちらを見てください。

MESHを使って鍵の閉め忘れをなんとかする - ほげほげ(仮)

ネスカフェ ドルチェグスト

12月のCyberMondayで半額くらいになってたので、勢いで買いました。

たまに美味しいコーヒー飲みたくなるので、これは手軽で非常に良いです。勢いで買ったけど、気に入ってます。

PlayStation®VR

PlayStation VR PlayStation Camera同梱版

PlayStation VR PlayStation Camera同梱版

かれこれ1ヶ月以上使ってないですが、良いです。未来を感じます。

友達が遊びに来て、サマーレッスンで一生懸命スカートの中を覗くのを横から眺める体験ができます。

REGZA 43Z700X

43インチの4Kテレビです。PS4Pro買うのにあわせて買いました。

前のが32インチだったので、画面も大きくなって満足度が高いです。

あと、今のテレビってHDDつけると録画してくれるんですね。今までBDレコーダーを使ってたんですけど、起動も動作も遅くて不満だらけでした。でも、今のテレビで録画もするようにしたらサクサク動いて快適です。BD見るのはPS4あるので、BDレコーダーは捨てようと思います。

PlayStaion4 Pro

PlayStation 4 Pro ジェット・ブラック 1TB (CUH-7000BB01)

PlayStation 4 Pro ジェット・ブラック 1TB (CUH-7000BB01)

品切れ続いてるみたいですが、ぼくは予約開始と同時に速攻でポチりましたので発売日に手に入れてます。

旧型と比較して、起動と読み込み速度は少し速くなったような気がします。あとはだいぶ静かになった気がします。

画質に関してはちゃんとPro対応しているソフトを比較してないのですが…

ちなみに今後購入予定のソフトは GRAVITY DAZE2、NieR:Automata、Horizon Zero Dawnです。

PS4まわりについては前回記事を書きましたので、読んでみてください。

PlayStation4 Proと周辺機器の話 - ほげほげ(仮)

まとめ

2016年はだいぶお金を使ってしまいました。特にPS4まわりはお金使いすぎた感じですね…

来年の予定としては、そろそろ家のMacを新調したいなぁ、ErgoDoxほしいなぁ、ってところですかね。

来年は節約したい…

PlayStation4 Proと周辺機器の話

PlayStation4 Proと周辺機器について雑に書きました。何か参考になれば。

PlayStation4 Pro

PlayStation 4 Pro ジェット・ブラック 1TB (CUH-7000BB01)

PlayStation 4 Pro ジェット・ブラック 1TB (CUH-7000BB01)

ぼくは2年くらい旧型を使ってて、予約開始と同時にポチっと購入しました。

今までのPlayStation4と比べて何が違うかは、よくある質問のほうをみると詳しく書いています。

PlayStation®4 Pro:よくある質問 | PlayStation®.Blog

で、Proは買ったほうがいいのか?

個人的には今まで持ってなくて新しく買う人はProを買ったほうが今後は良いと思います。スリム版の1Tと比べて、1万円ほど高いですが、機能的な差は1万円以上だと思います。

4KテレビじゃないとProのほうがパフォーマンスが落ちるソフトもあるようなので、4Kテレビ持ってない人は微妙かもしれないですが…

現状で持っている人は悩むところだと思います。特に1年以内とかに買った人は。

ぼく個人の考えだと、4KテレビかPSVRを持っていない場合は特に買い替えてもそこまで恩恵はないかなと考えます。

もし、4Kテレビを買おうと考えてる人は先に買っておくのは全然アリだと思います。

テレビ

ぼくはProにあわせてテレビ新調しました。そのとき出て来る用語とかをざっくり書き出しておきます。あと注意点とか。結構ややこしい感じです。

4K/HDR

Proのほうは4K/HDRに対応しています。なのでテレビもそれに合わせたものを買うのが良いと思います。

ここで注意としては、4K = HDRではない ことです。もしProを買ってテレビやモニターを買おうと考えてる人は気をつけてください。4Kには対応してるけど、HDRには対応していないものあります。

おそらく現状でPCモニターでHDRに対応しているものはないと思います。安いからPCモニターの4KにするとHDRの恩恵は受けられません。よく考えたほうが良いポイントです。

テレビが4K/HDRに対応してるかはPS4の設定から確認できます。Youtubeのほうで確認方法を見ておくと良いです。


"PlayStation 4 Pro" 映像設定チュートリアルビデオ

HDR

HDRHigh Dynamic Rangeの略で、輝度の幅を拡大するものです。映像を現実世界の明るさに近づける技術です。

ぼくも全然詳しくはないのでうまく説明ができないですが、HDRの有り無しで光の表現が全然違ってきます。

4Kはもう時代遅れ? テレビの新しい高画質「HDR」とは何なのか - 日経トレンディネット

HDMI

さらにややこしい仕様のHDMIについてです。これは先程4K/HDRに関わる部分なので結構重要です。

まず、HDMIってケーブルの名前ではなく通信インターフェース規格です。あのケーブルはこの規格に則っとたものになります。

HDMIのバージョンとかあるのですが、あまり詳しくないので、Wikipediaとかを見てもらったほうが早いです。

HDMI - Wikipedia

で、何が言いたいかというと、このHDMIがちゃんとしてないとPlayStation4 Proの恩恵を受けられないので注意が必要です。

HDMIケーブル

よくある質問のほうにも書いてあるのですが、プレミアム ハイスピードHDMIケーブルを使うことで、4K出力と60フレームに対応できます。

1本は同梱されてるのでそれを使えば良いと思いますが、もし長さ足りないなどの理由で買い換える場合は、プレミアム ハイスピードHDMIケーブルの認証を受けたものを買うようにしましょう。

4K対応「Premium HDMIケーブル」の認証プログラムがスタート - AV Watch

HDMIバージョン

最新バージョンはHDMI2.0ですが、最近のテレビはほぼ対応しているので問題はないかと思います。

ただ、HDR対応しているものは2.0aになると思います。

HDMI 2.0a発表。HDR映像伝送に対応 - AV Watch

HDCP

テレビの仕様とかでみかけるHDCPですが、これは著作権保護の技術になります。

最新のバージョンはHDCP 2.2で、4K放送をみるために必要なものになります。

最近のテレビは対応されてると思うので、特に気にする必要はないかと思います。

4K TVで4K放送を見るために必要な「HDCP 2.2」とは? 各社対応まとめ - AV Watch

テレビはどれがおすすめ?

ぼくが買ったものですが、REGZA 43Z700X になります。

本当はREGZA Z20Xが良かったのですが、50インチからになるので、さすがに部屋に入らないです。

迷ったのがREGZA M500Xです。こちらも4K/HDRに対応してますし、Z700Xより安価なタイプになります。こちらも悪くないとは思います。

だいたいゲームでのおすすめテレビをググるREGZAは必ずあがってきます。理由は遅延時間が少なくゲームに適しているからです。REGZA自体もそれを売りにしている感じですね。

あとはSonyということでBRAVIA候補にあがってきます。

結構忘れがちなのがHDR対応かどうかです。HDRのON/OFFで映像の明るさが変わってきます。好みの問題ではありますが、可能であれば対応したものを選ぶようにしたほうが良いと思います。

困ったら電気屋さんとかで聞いてみたり、見比べたりすると良いと思います。

注意

テレビによっては相性が悪いものがあるみたいなので、事前に調べておいたほうが良いと思います。

ソニーPS4 Proに不具合 TVメーカーと「責任のなすり合い」状態に | Forbes JAPAN(フォーブス ジャパン)

REGZAの注意

REGZAはそのままだと4K/HDRに対応してない状態になっているので設定の変更が必要です。

以下のブログに設定変更の方法がまとめられています。

東芝のREGZA Z700XとPS4®Proを接続してHDRの4K対応させた - 初老のボケ防止日記

PlayStation VRの注意

PSVRを使ってる人はひとつ注意があって、PSVRのプロセッサユニットはHDRをパススルーしません。なのでHDR対応したゲームをプレイする場合はPSVRから外して、テレビへ直接つなぐ必要があります。

HDMIセレクタ

HDMIセレクターとかで切り替えしている場合も注意が必要で、4K/HDRに対応しているか確認したほうが良いです。

試してないはないですが、このセレクターは対応してる感じします。未検証なので購入する方はしっかり検討してください。

SSD

次にSSDです。ぼくが観測した感じだと結構SSDに交換した人が多いように感じました。

ぼくは旧型でもSSDに交換して使ってたのですが、ProもSSDに交換しました。

ProではSATA3に対応したので(旧型はSATA2)、SSDに交換することの効果が高いと考えられています。交換自体も簡単です。

実際に交換後は体感的にだいぶ速くなってる感じです。

少しでもロードを速くしたい人は交換したほうが良いと思います。HDDのままでも旧型よりは速くなったような比較もあるので、普通に遊ぶには問題ないと思います。

今後Proに対応したゲームとかだと更にロードが遅くなる気もしてるのでSSDの効果に期待しています。

ヘッドセット

今使ってるのは、ソニーがだしているPS4用のヘッドセットです。基本これで問題ないと思います。

バーチャル7.1chなので音がどの方向からしたのかわかります。特にFPSプレイヤーにとっては必須な機能だと思います。

ボイスチャットも特に問題ない感じです。

ワイヤレスですが遅延を感じたことはないです。

ワイヤレスサラウンドヘッドセット

ワイヤレスサラウンドヘッドセット

来年それの後継機にあたるのかな、新しいヘッドセットが発売される予定です。3Dオーディオに対応してるので、PSVRでも使えると思われます。これはどうするか悩み中です。

まとめ

お金使いすぎた

やさしいDagger2

Androidその2 Advent Calendar 2016 - Qiita 2日目の記事です。

Dagger2は最初のハードルが高くてなかなか導入できなかったり、メリットがよく分からなかったりします。そういう方が雰囲気だけでも掴めれるように、簡単なサンプルを実装しながら確認できるようなのを書きました。

だいぶ長くなってしまった感じですが、あまり複雑なことはやっていないつもりです。

セットアップ

build.gradle に依存関係を追加します

compile 'com.google.dagger:dagger:2.7'
annotationProcessor 'com.google.dagger:dagger-compiler:2.7'

Hello Dagger2

すごくシンプルなサンプルです。

依存解決されるクラス

まずは依存解決されるクラスを作ります。Dogクラスとします。

public class Dog {
    public String getName() {
        return "ぽち";
    }
}

インスタンスを提供するクラス

依存を解決させるためにインスタンスを提供する必要があります。その設定をしていきます。

クラスには@Moduleをつけて、インスタンスを提供するメソッドには@Providesをつけます。

@Providesの戻り値は注入したい依存の型になります。今回はDogクラスを注入したいのでDogが戻り値になっています。

@Module
public class SampleModule {
    @Provides
    Dog provideDog() {
        return new Dog();
    }
}

依存解決のためのinterface

依存解決をしたいところと、どのモジュールを使うかを定義します。

@Componentの引数には使用するModuleクラスを設定します。

injectメソッドを定義して、引数には依存解決を実行するクラスを設定します。今回はMainActivityで依存解決を行います。

ビルドするとDaggerSampleComponentクラスが自動生成されます。これを次に使ってきます。

@Component(modules = SampleModule.class)
public interface SampleComponent {
    void inject(MainActivity activity);
}

実際に依存解決してみる

MainActivityを次のようなコードにします。よくあるサンプルではApplicationクラスでやってますが、とりあえずシンプルにAcitivtyで直接行います。

やってることはコードのほうにコメントしていますが、基本的にはインスタンスを注入してほしいものに@Injectをつけて、Componentを作って注入を実行する感じです。

今回はSampleModuleで設定したDogクラスを戻り値に設定したものが注入されます。

public class MainActivity extends AppCompatActivity {

    // インスタンスが注入されるフィールド
    @Inject
    Dog dog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // SampleComponentからDaggerSampleComponentが自動生成されるので、それを使ってSampleComponentを作ります。
        SampleComponent component = DaggerSampleComponent.builder()
                // 使用するModuleのインスタンスを指定します。
                // (ここでdeprecatedになることがありますが、一旦すべてコードを書いてビルドすると消えると思います)
                .sampleModule(new SampleModule())
                .build();

        // 依存の注入を実行します
        component.inject(this);

        String name = dog.getName();

        Log.d("MainActivity", name);

    }
}

確認

ここまで実装して、実行するとログが出力されと思います。

これだけでは何のためにあるのかよくわからないと思いますが、とりあえずここでは、依存を解決するために、ModuleとComponentが必要だということを抑えておけば良いです。

Field InjectionとConstructor Injection

さきほどは少し違う依存解決方法を簡単にやります。

前回やったのは、クラスのフィールドに@Injectをつけてそこに注入したので、Field Injectionになります。

もう一つが、Constructor Injectionになります。コンストラクタのパラメータとして依存を注入します。

コンストラクタに注入されるクラス

新しくOwnerというクラスを作ります。

コンストラクタに先程のDogクラスをパラメータとして受け取るようになっています。さらに@Injectをつけています。

public class Owner {

    private Dog dog;

    @Inject
    public Owner(Dog dog) {
        this.dog = dog;
    }

    public String getPetName() {
        return dog.getName();
    }
}

MainActivityを修正

次のように修正します。

変更点はフィールドがDogからOwnerに変わりました。

OwnerについてはModuleに設定したりしてませんが、Dagger2が勝手にコンストラクタに注入してOwnerインスタンスを作ってくれています。

public class MainActivity extends AppCompatActivity {

    @Inject
    Owner owner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SampleComponent component = DaggerSampleComponent.builder()
                .sampleModule(new SampleModule())
                .build();

        component.inject(this);

        Log.d("MainActivity", owner.getPetName());

    }
}

確認

ここまで出来たら、実行して確認します。結果は先程変わっていませんが、Ownerの処理からDogの処理を呼ぶようになりました。

Constructor Injectionを使うとDagger2がModuleに設定されたものを使って、インスタンスを作ってくれます。

ぼくはこれがかなり便利だなぁって思っています。ContextとかをModuleに設定しておいて使いたいクラスのコンストラクタでパラメータにすることをよくやります。

interfaceを使う

これまでの使ったのは実装クラスでしたが、次はinterfaceを使うように変更します。

新しいinterfaceをつくる

新しくPetというinterfaceを作ります。

public interface Pet {

    String getName();

}

そして、Dogがそれを実装する形に修正します。

public class Dog implements Pet {

    @Override
    public String getName() {
        return "ぽち";
    }
}

interfaceを使うように修正していく

まずはModuleを修正します。

@Module
public class SampleModule {

    @Provides
    Pet providePet() {
        return new Dog();
    }
}

次にOwnerのパラメータを変更します。

public class Owner {

    private Pet pet;

    @Inject
    public Owner(Pet pet) {
        this.pet = pet;
    }

    public String getPetName() {
        return pet.getName();
    }
}

確認

実行して確認します。結果は変わっていません。

ここで確認するポイントはOwnerのクラスになります。

今までDogという実装クラスに依存していましたが、interfaceに変更することでそことの依存がなくなり、interfaceのみに依存する感じになりました。

Build Variantsで動作を変更する

Build Variantsで動作を変更するようにします。Build Variants自体についはドキュメントやググったりしてください。

今回はProductFlavorsで分けるようにします。

build.gradle

build.gradleを下記のようにして、dogcatのProductFlavorを作ります。

android {
    ...
    productFlavors {
        dog {
        }
        cat {
        }
    }
}

フォルダ作成

srcフォルダの下にdog/javaのフォルダを作り、さらにパッケージを作成しておきます。同様にcat/javaも作成しておきます。

たぶん、どちらかのjavaフォルダがソースフォルダとして認識されないと思います。メニューのView → Tool Windows → Build VariantsからBuild Variantを切り替えることで直ると思います。

実行するときもBuild VariantsのWindowから切り替えることになります

f:id:STAR_ZERO:20161105094229p:plain

Catクラス

新しくPetinterfaceを実装したクラスを追加します。これはmainフォルダのソースフォルダに追加します。

public class Cat implements Pet {

    @Override
    public String getName() {
        return "たま";
    }
}

main

mainフォルダ内にあるSampleModuleクラスを削除しておきます。

BuidTypeやProductFlavorでmainフォルダ内に同一クラス名を使うことはできないためです。代わりにdogフォルダとcatフォルダにSampleModuleを追加するようにします。

catのProductFlavor

catのProductFlavor実行時には先程のCatクラスを使うようにします。

catフォルダのソースフォルダに下記を追加します。今まで異なるのはDogクラスのインスタンスを返却するのではなく、Catクラスを返却するようにします。

@Module
public class SampleModule {

    @Provides
    Pet providePet() {
        return new Cat();
    }
}

f:id:STAR_ZERO:20161105094307p:plain

dogのProductFlavor

dogのProductFlavorの場合は今までのDogクラスを使います。

dogフォルダのソースフォルダに下記を追加します。

@Module
public class SampleModule {

    @Provides
    Pet providePet() {
        return new Dog();
    }
}

f:id:STAR_ZERO:20161105094319p:plain

確認

Build VariantsのWindowでcatDebugdogDebugを切り替えて実行してみてください。

それぞれ、違う結果になると思います。

Build Variantsで実装クラスを丸ごと差し替えて、それぞれで違う動作が可能になります。BuildTypeのreleaseとdebugで処理を分けたい場合や、ProductFlavorの有料版と無料版で処理を分けたい場合などに使えます。

テストを書いてみる

Ownerクラスのテストを書いてみます。このサンプルコードだとDagger2なくても問題なくかけると思いますが…

テスト

テストを次のように書いてみます。androidTestのほうではなくて、testのほうです。

Ownerに渡すパラメータは匿名クラスとしてその場で生成しています。

今回の場合はあまりメリットを感じないかもしれないですが、直接Dogインスタンスを渡してません。

例えばDogクラスのgetNameAPI通信している場合にDogクラスに直接依存している状態だとテストを書くのが結構ツライ感じになったと思います。さらに、もしDogクラスのコンストラクタでContextが必要だったりするとかなり厳しい感じだと思います。

今回のようにinterfaceを使ってテスト時は動作を変更することで、Ownerのテストが書きやすくなりました。

public class OwnerTest {

    @Test
    public void getPetName() throws Exception {
        Owner owner = new Owner(new Pet() {
            @Override
            public String getName() {
                return "ペットの名前";
            }
        });

        assertThat(owner.getPetName(), is("ペットの名前"));
    }
}

確認

テストを実行してみてパスすることを確認します。

Constructor Injectionを使ってたおかげで、テストがだいぶ書きやすくなりそうな感じです。

まとめ

今回のサンプルはあまり実践的ではないかと思いますが、雰囲気を分かってもらえれば嬉しいです。

Dagger2のテストのサンプルとかだと結構UIについてが多かったりしますが、UI以外のテストにもとても有効だと思いますし、導入がしやすいかなぁって思います。

実際に手を動かして確認しないと理解しにくのもあるので、Dagger2を試したことない人は一度やってみたほうが早いかなぁって思います。複雑な部分もありますが、単純な使い方だとそこまで苦労せず使えると思います。(ぼくは単純な使い方しかできないですが…)

ToolbarはFragmentに持たせても良いと思う

Y.A.M の 雑記帳: Fragment に Toolbar を持たせるのはやめなさい を読んで思ったことです。

ぼくはFragmentにToolbarを持たせても良いと思ってます。

もちろんActivityに置くことでFragment側がシンプルになるので、極力そうしたい感じですが、色々と事情があります。

例えばFragmetごとにToolbarに独自のViewを入れたりしたい場合があります。記事では android:actionViewClass を使えば良いと書いていますが、Toolbar使う理由ってレイアウトファイルでToolbar内を簡単にカスタマイズできることだと思っています。Toolbar内をカスタマイズしたほうがコード自体も見通しが良くなる場合もあると思います。もちろんSearchView などの便利なやつは android:actionViewClass で使いますが。

あとはDataBinding絡みで簡単にToolbar内のViewにアクセス可能になります。 android:actionViewClassでもDataBindingUtil.bindを使えばいけるかもしれないですが、わざわざ複雑さを増すようなことはしたくないなぁと個人的には思います。

じゃ、Googleはどうしてるの?ってのを思って調べたら、googlesamplesで実装を見かけました。揚げ足取りみたいな感じでアレですが…

https://github.com/googlesamples/friendlyping/blob/ee8e2bb4084b1326f414903ca0e4b1189489ad5c/android/app/src/main/java/com/google/samples/apps/friendlyping/fragment/FriendlyPingFragment.java#L169

こういうのは難しいところで、正解は無いと考えています。

今回は賛同できませんでしたが、そういう考えで実装を進めることは非常に良いと思います。

すいません、少しモヤモヤしたところあったので書かせてもらいました。