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

ほげほげ(仮)

仮死状態

やさしい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

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

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

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

MESHを使って鍵の閉め忘れをなんとかする

MESHとは

MESH:遊び心を形にできる、アプリとつなげるブロック形状の電子タグ|ソニー

さまざまな機能を持ったブロック形状の "MESHタグ" を "MESHアプリ" でつなげることにより、 あなたの「あったらいいな」を実現できる それがMESHです。 難しいプログラミングや電子工作の知識は必要ありません。 IoT(モノ・コトのインターネット化)を活用した仕組みも 簡単に実現します。

とのことです。

去年くらいからあったっぽいのですが、ぼくは全然知らなくて、この前ソニーストア行った時に見て興味が湧いて実際に買ってみました。

iOSアプリを使って、アイコンを繋いでいくだけで様々なことが出来ます。

すごく簡単なので、子供がいらっしゃる方は子供と一緒にやる良いかもしれません。夏休みも残り少ないけど自由研究的なものに。

いろんな人がレシピを公開してるので、これを見るとどんなことが出来るのかイメージしやすいかもしれません。 MESHレシピ

MESHアプリ

iOSアプリで下のような感じでアイコンをつなぐだけで色々できます。下の例だと、動きタグが何か検知したらiPhoneの音が鳴ります。

f:id:STAR_ZERO:20160819141005p:plain:w200

詳しくは下のリンクを参考にしてください

MESHアプリ Creative DIY Toolkit|ソニー

鍵の閉め忘れをなんとかしたい

ぼくは鍵を閉めたのかが不安になることが多くて、一度家に戻るとかもよくやります。

今回はMESHの検証ついでにこの課題をなんとかしたいと思います。

鍵の開け閉めを検知する

MESH-100AC 動き(Move)タグ - 動きがきっかけになる新しい感覚 ワイヤレス加速度センサー | ソニー

今回は動きタグのみを使って、鍵の開閉を検知する仕組みを作ります。

これ想像以上に優秀で、振られたら、ひっくり返したら、振動したら、向きが変わったら、を検知できます。

今回はこの向きが変わったらをトリガーにします。

ドアの鍵にこんな感じにくっつけます。見た目がかなり残念な感じですが…

  • 開けた状態

f:id:STAR_ZERO:20160819135325j:plain:w200

  • 閉めた状態

f:id:STAR_ZERO:20160819135333j:plain:w200

鍵が閉められたのを検知

動きタグが表向きに変更されたら、鍵を閉めたと判断します。

キャンバスに動きタグを配置して、タップすると詳細が見れるので、そこで「向きが変わったら」の「表」を選択します。

f:id:STAR_ZERO:20160819141651p:plain:w200

鍵が開けられたのを検知

これも同じような感じで設定します。

「向きが変わったら」の「上」を選択します。

f:id:STAR_ZERO:20160819141905p:plain:w200

これで鍵の開け閉めは検知できるようになりました。

通知する

次は通知です。おそらく現状でMESHの状態(動きタグの例だと今は表を向いてるとか)が簡単に取れないと思うので、通知でログを残す方法で状態を確認できるようにします。

MESHでは、スピーカー鳴らしたり、iPhoneに通知をだしたりできますが、これではログとしてはあとから確認するには不十分です。

MESHは実はIFTTTと連携することができます。これを使ってSlackに投稿させればログとして残るし後から確認できます。

IFTTT設定

ここは省略します。詳しくは公式サイトの手順を参考にしてください。

IFTTT連携の初期設定方法を教えてください – MESHサポート | 遊び心を形にできる、アプリとつなげるブロック形状の電子タグ

IFTTT連携でレシピの作り方を教えてください – MESHサポート | 遊び心を形にできる、アプリとつなげるブロック形状の電子タグ

ぼくはSlack側の設定は下のような感じにしました。

f:id:STAR_ZERO:20160819143056p:plain:w200

Messageのところを{{Text}}にしておくと、MESHアプリからテキスト設定できます。

アプリ設定

アプリの右下にある連携タグからIFTTTを選択して、キャンバスへ配置します。

IFTTTの設定を下のようにイベントIDとテキストを設定します。

f:id:STAR_ZERO:20160819143304p:plain:w200

同じように開けられた時のIFTTTもキャンバスへ配置しておきます。

それを動きタグと繋ぎます。

最終的にキャンバスは次のようになります。上が鍵を閉めた時、下が鍵を開けた時になります。

f:id:STAR_ZERO:20160819143652p:plain:w200

確認

鍵の開け閉めをするとSlackに次のように通知されます。

f:id:STAR_ZERO:20160819144300p:plain:w200

これで、鍵の状態が確認できるようになりました。

完成

コーディングなしで簡単にここまで出来ました。工夫次第ではもっと色々できると思います。

MESHがくっつけられればだいたいの鍵に対応できると思います。オフィスとかで最終退出時間を自動で残すとかにも使えるかも。

まとめ

MESHは他にも色々な使い方が出来ると思うので、時間あるときにまた試そうと思います。またSDKもあるっぽいのでそれも見てみたいと思います。

MESHは単品でも買えるので、お試しで一つだけっていう感じで気軽に試せます(それなり値段はしますが…

暇な人はこれで遊んでみると良いと思います。

MESH Moveタグ

MESH Moveタグ

ワイヤレスファンクショナルタグ Button

ワイヤレスファンクショナルタグ Button

MESH Bundle 7

MESH Bundle 7

CQRSのメモ

設計

CQRSって知らなかったので調べました。かなり雑なメモです。

参考リンクとかではEvent Sourcingについても絡んできますが、ここでは触れてないです。

参考

ここにある記事を読みました。英語のとこはなんとくで読んだので怪しいですが。

ぼくのこのメモを読むより、下記の記事を読んだほうがいいです。

CQS(Command Query Separation)

CQRS(コマンドクエリ責務分離)に入る前に、CQS(コマンドクエリ分離原則)について。

CQSはコマンドとクエリを分けようってことです。

  • クエリ: 結果のみを返し、状態の変更は行わない
  • コマンド: 状態の変更のみを行い、結果は返さない

これをオブジェクトレベルで適用します。メソッドで分ける感じです。

ただ、Javaiteratorのようにnextで結果の取り出しと内部状態の変更をやっているものもあります。こういうイディオムは便利なので原則を破ることもあるようです。

CQRS(Command and Query Responsibility Segregation)

CQSではオブジェクトレベルで適用する感じでしたが、CQRSではもっと広い範囲に適用します。Event Sourcingも含むとアプリケーション全体になるかと思います。

CQRSではCQSと異なり、コマンドとクエリをメソッドで分けるのではなくオブジェクトごと分けます。

例えばこういうオブジェクトがあった場合、

CustomerService
void MakeCustomerPreferred(CustomerId)
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)

次のように分離します。

CustomerWriteService
void MakeCustomerPreferred(CustomerId)
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)

CustomerReadService
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()

クエリ側

データを取得するメソッドを持つだけで、各画面にマッチするDTOを生成して返却します。

多くのドメインではクエリ最適化が困難、インピーダンスミスマッチ問題などがあります。

CQRSを適用すれば、クエリ側はドメインを使いません。新しい概念の「Thin Read Layer」でデータベースから直接読み込んでDTOに反映します。

コマンド側

読み込みをドメインから分離すれば、コマンドの処理だけに焦点があたります。

リポジトリではGetByIdを除くとクエリはわずかになります。

クエリを分離したドメインモデルで作業をすれば、概念的なオーバヘッドが減り、コスト削減につながります。

(ここらへん怪しい)CQRSを適用すると、クエリとコマンドで同一データモデルを使うのかという問題がある。ここから先はEvent Sourcingの話っぽい?(ココのP23の下の部分)

まとめ

CQSはすぐにでも適用できそうですが、CQRSになると簡単にはいかなさそうです。

Event Sourcingも絡んでくると、ぼくはお手上げ状態になりそうです。

ここの最後にも書いてあるのですが、CQRSについてはだいぶ慎重になったほうが良さそうです。

XCTestでthrowsをテストする

Swift

環境

テスト対象コード

テストの対象となるサンプルコードです。

エラータイプの定義です。

enum SampleError: ErrorType {
    case Invalid
    case Unknown
}

エラーをthrowするコードです。

class Sample {
    static func exec() throws {
        throw SampleError.Invalid
    }
}

テスト

throwされたかをテストする

エラーがthrowされた場合はテストはパスします。もしthrowされなかった場合はテストは失敗します。エラーのタイプなんでもいいです。

XCTAssertThrowsError(try Sample.exec())

throwされたエラーもテストする

Closure内でエラーが期待しているものかをXCTAssertEqualを使ってテストします。もし、違うタイプのエラーの場合はテストは失敗します。

XCTAssertThrowsError(try Sample.exec()) { error in
    XCTAssertEqual(error as? SampleError, SampleError.Invalid)
}

Firebase Cloud Messagingで通知をカスタマイズ(Android)

Android

Firebase Cloud Messaging (FCM)を試して、通知のカスタマイズについて調べてみました。

FCMとNotification

FirebaseにはFCMとは別にNotificationという似たようなのがあります。

この辺りの違いは下記の記事にまとまってます。

Firebaseによるプッシュ通知のハマりどころ - Qiita

ざっくりと言って、Web上のConsoleから通知を発行できるのがNotificationで、FCMはその基盤になっておりAPI経由で実行する感じかと思います。

概要

今回はNotificationは使いません。

というか、Notificationでは通知のカスタマイズが微妙に物足りません。ぼくは特に通知アイコンを変更できないのがツライです。

現状だとAPIを叩くしか方法はありません。

通知を受けた時の処理

通知をカスタマイズするには通知を受けた時に自分で通知を表示すればいいと思うのですが、ここの挙動がやっかいなので、軽く説明が必要になります。

ここのドキュメントに書いてる通り、FirebaseMessagingServiceを継承してonMessageReceivedをoverrideすれば通知受信時に処理が可能です。

ただし、onMessageReceivedが実行されるには、アプリの状態と通知がNotificationなのかDataなのかそれとも両方なのかで変わってきます。

onMessageReceivedは通知を受けたら必ず呼ばれるものではなく、条件で呼ばれないことがあります。あと自動で通知が表示されたりする場合もあります。

f:id:STAR_ZERO:20160529161944p:plain (https://firebase.google.com/docs/cloud-messaging/downstream から引用)

App state、Notification、Data、Both

さきほどのドキュメントの図が分かりづらいので、簡単に説明しておきます。

App stateはアプリ状態でForegroundBackgroundのいずれかです。

NotificationDataBothですが、APIの使い方で変わります。

言葉で説明しにくいのでAPIへ投げるJSONの例で簡単に。最低限の設定でやってます。

APIの仕様はここを見てください。

Notification
{
  "to" : "/topics/all",
  "notification": {
    "body": "メッセージ"
  }
}

この時、アプリの状態がForegroundの場合はonMessageReceivedが呼び出されますが、Backgroundの場合はSystem trayと記載されてる通り通知がすぐ表示されて、onMessageReceivedは呼び出されません。

Data
{
  "to" : "/topics/all",
  "data": {
    "hoge": "foo"
  }
}

この時は、アプリがForegroundBackgroundでもonMessageReceivedが実行されます。 dataに設定した値はonMessageReceived内で処理します。

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    Log.d(TAG, "Notification data: " + remoteMessage.getData().get("hoge"));
}
Both

これはJSONnotificationdataもある状態ですね。

{
  "to" : "/topics/all",
  "notification": {
    "body": "メッセージ"
  },
  "data": {
    "hoge": "foo"
  }
}

アプリがForegroundの場合はonMessageReceivedが実行されます。

Background時が少し複雑です。Notificationと同じように通知は表示されますが、onMessageReceivedが実行されません。では、dataに設定された値はどこで処理するのか?これは通知タップで起動されるActivityのintentに入ってくるのでそこで処理するようにします。

通知のカスタマイズ

少し前置きが長くなりましたが、通知のカスタマイズ方法です。

自分で通知を表示する

dataのみをJSONに含んでAPIを呼んであげれば良いです。

そうするとBackground状態でも、onMessageReceivedが呼ばれるので、NotificationCompat.Builderを使って自分で通知を表示することが可能になります。

APIで設定する

ここnotificationに設定できるキーがあります。

iconsoundを設定することである程度はカスタマイズ可能です。

{
  "to" : "/topics/all",
  "notification": {
    "title": "タイトル",
    "body": "メッセージ",
    "icon": "ic_notification"
  }
}

ただし、Foregroundの時でも通知を表示したい場合は、onMessageReceivedNotificationCompat.Builderの実装が必要になります。

まとめ

通知をちゃんとカスタマイズするには、FCMのAPI経由しかない状況です。あと、Notificationと違ってConsoleに履歴とか残らないのも問題です。(どこかにあるのかな?)

通知を発行するのがエンジニア以外だと、APIを直接投げるようなことは難しいでしょうし、そのあたりも考えないとダメですね。

Firebaseは便利は便利だと思いますが、通知だけやりたいって状況でFirebaseは微妙に合わないかもしれません。個人的はOneSignalのほうが通知に関しては簡単で便利かなと思います。

参考

RecyclerViewでDataBindingを使う

Android

RecyclerViewでDataBindingを使用する簡単なサンプルです。

Gradle

DataBindingの設定をしておきます。

android {
    // ...

    dataBinding {
        enabled = true
    }
}

データクラス

サンプル用に次のような簡単なデータクラスを用意します。

public class Item {

    public final String name;

    public Item(String name) {
        this.name = name;
    }
}

レイアウト

リスト1行のレイアウトです。

基本的には通常のDataBindingと同じです。TextViewに先ほど作ったデータクラスの値を表示します。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="item"
            type="com.example.Item" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{item.name}"/>
    </LinearLayout>
</layout>

Adapter

Adapterのコードです。

onCreateViewHolderでBindingクラスを取得し、それをViewHolderに渡して保持するようにしてます。

表示はonBindViewHolderでBindingクラスに対して変数をセットします。その後、executePendingBindingsを呼び出して即座に反映するようにしています。

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    private List<Item> items;

    public RecyclerAdapter(List<Item> items) {
        this.items = items;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // DataBinding
        ListItemBinding binding = ListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Item item = items.get(position);

        // データセット
        holder.binding.setItem(item);

        // Viewへの反映を即座に行う
        holder.binding.executePendingBindings();
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        final ListItemBinding binding;

        public ViewHolder(ListItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }
}

Activity or Fragment

ここまで実装したらあとはActivityなりFragmentなりで使用するだけです。

今回はActivityの例です。レイアウトファイルは省略で。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding =  DataBindingUtil.setContentView(this, R.layout.activity_main);

        List<Item> items = new ArrayList<>();
        items.add(new Item("Item1"));
        items.add(new Item("Item2"));
        items.add(new Item("Item3"));
        items.add(new Item("Item4"));
        items.add(new Item("Item5"));

        RecyclerAdapter adapter = new RecyclerAdapter(items);
        binding.recycler.setLayoutManager(new LinearLayoutManager(this));
        binding.recycler.setAdapter(adapter);
    }
}

ドキュメント