Dagger Hilt ことはじめ

先日 Hilt のアルファリリースとドキュメントが公開されました。 最近 Dagger を触っている身としてはどうしても気なり、dagger.android を利用している Android プロジェクトで Hilt への移行を試しました。

今回の環境は 2020/6/16 時点で hilt-android(2.28-alpha) および androidx.hilt(1.0.0-alpha01) を使用しました。

dagger.android からのマイグレーション

アルファリリースが行われた時点ですでに豊富なドキュメントが公開されています。 dagger.android からのマイグレーションにおいては公式から以下のドキュメントが公開されています。

dagger.dev

自分は参考にしませんでしたが、CodeLab も公開されています。

codelabs.developers.google.com

今回は具体的な移行手順の説明ではなく、移行する上でのポイントとつまずいた内容をまとめてみました。

移行する上でのポイント

これまで自身で実装する必要があった多くのものを Hilt が提供してくれるようになります。 特に Component と Activity や Fragment ごとに作成していた SubComponent を(難しいことをしなければ)実装する必要がなくなりました。 これにより多くの実装が減り、構造が少し変化しています。

@HiltAndroidApp @AndroidEntryPoint の登場

Hilt では Application に @HiltAndroidApp を付与するだけで、提供される Component の仕組みにのることができます。 これまでは Component の作成をせっせとやっていましたが、複雑な Component を作る必要がなければこれだけで完結します。

Activity や Fragment での SubComponent の作成も @AndroidEntryPoint のみですべてが解決するようになりました。 AndroidInjection#inject@ContributesAndroidInjectorHasAndroidInjector の実装がすべて必要なくなりました。

仕組みとしてはアノテーションプロセッサによって SubComponent の生成や依存注入の実装を持った中間クラスを生成し、継承関係に滑りこませるようなことをしているようです。

Component と Module の関係の変化

これまでは自作した Component から Module を指定し、依存グラフを構築するという以下のような関係で実装をしていました。

f:id:ksfee:20200616035058p:plain
Component が参照する Module を指定

Hilt では Component を基本的に自作する必要がなく、提供されるものを利用します。 しかし提供される Component は開発者が直接扱うことはできず、これまでのように Module を指定するということができません。

そのためこれまでとは逆に Hilt では Module 自身が所属する Component を @InstallIn アノテーションによって指定するようになりました。

@InstallIn(ApplicationComponent.class)
@Module
interface NetworkModule {
    ...
}

その結果 includes で Module をまとめていたいわゆる Aggregation Module のような存在が必要なくなり、以前より Module がフラットに並ぶような構造となりました。

f:id:ksfee:20200616035436p:plain
Module が Component を指定するように

Module の @InstallIn アノテーションによる Component の指定は必須となっており、存在しない場合コンパイル時にエラーとなります。 どうしても includes を利用して自身では Component の指定を行いたくない場合は、@DisableInstallInCheck アノテーションによって回避可能になっています。 他にもオプションによってコンパイル時のチェック自体をやめることも可能です、詳しくはドキュメントを参照してください。

Hilt では ApplicationComponent のように提供される Component がいくつか存在します。 こちらも詳しくは公式ドキュメントを参考にしてください。

Component 初期化時の引数

これまで自作していた Component では BuilderFactory 内で値を挿入し、依存グラフの中で参照することができました。 この機能も提供される Component を直接扱えない関係上、作成時に値を付与することができません。

主な代替手段としては Module に置き換えて依存グラフ上で参照可能にすることです。 Application や Context(ApplicationContext) などは提供される Component ごとに Hilt がバインディングしてくれる値があるので、そちらを利用することになります。

一応カスタム Component をバインディングする値を増やすことはできるので、どうしてもという場合はドキュメントを参考に実装してみてください。

@ViewModelInject の導入

ViewModel では Jetpack integration として提供されている @ViewModelInject を利用します。 AssistedInject を踏襲するようなインターフェイスとなっており、ViewModelProvider を自作する必要がなくなり、 KTXviewmodels を利用するだけで簡単に依存注入を行えるようになりました。

Android developers にドキュメントが公開されているので詳細はそちらを参照してください。

つまずいた個所

Activity, Fragment の実体をどう扱うか

dagger.android では @ContributesAndroidInjector によって SubComponent を生成し、その SubComponent では値を注入する対象であるクラスをそのまま扱うことができました。

Hilt では各 Component ごとにデフォルトでバインディングされた値を android.app.Activityandroidx.fragment.app.Fragment として依存グラフで扱うことができますが、具体的なクラスとして扱うことができません。

具体的には以下のような問題が発生すると考えています。

// Activity
class SampleActivity : AppCompatActivity(), ViewInterface {}

// Module
@InstallIn(ActivityComponent::class)
interface SampleModule {
    @Binds
    fun bindView(view: SampleActivity): ViewInterface  // SampleActivity が依存グラフに存在しない
}

解決するための手段はいくつか考えましたが、Hilt の仕組みから外れず実装コストを最小減に抑えられる方法としてキャストを利用するようにしてみました。

@InstallIn(ActivityComponent::class)
object SampleModule {
    @Provides
    fun provideView(activity: Activity): ViewInterface = activity as SampleActivity
}

ただ少し強引すぎる手段なので、より良い方法を模索中です。

Bundle の値を ViewModel で受け取りたい

Hilt では ViewModel は ActivityRetainedComponent 下で依存注入が行われますが、現時点では Activity, Fragment と一切関係を持てないため Bundle から値を抽出して受け渡すというようなことができません。

AssistedInject の機能を搭載する予定があるようなので、そちらに期待しています。 現状の仕様ではほぼ解決することができないように思えるので、おとなしく機能拡充を待つことになりそうです。

medium.com

アノテーションプロセッサが静かに失敗する

kapt のタイミングでエラーが発生しても kapt の Gradle タスクがエラーをうまく出せず、タスクが正常終了してしまう問題に遭遇しました。

この問題は Gradle に --debug オプションを渡し、吐き出されるログを見続けることで、依存が足りずエラーが出ている個所を発見し解決しました。 具体的には ViewModel integration の依存(androidx.hilt:hilt-lifecycle-viewmodel)が Application モジュールに存在していないことが問題でした、ぜひ気を付けてください。

CustomView

dagger.android では依存注入のエントリーポイントは Activity, Fragment, Service, BroadcastReceiver を対象としていましたが、Hilt では Custom View 向けにもインターフェイスが用意されています。

しかし現行のアルファバージョンでは利用している JavaPoet 側に問題があり、@AndroidEntryPoint によって生成されるクラスで Nullable, Nonnull アノテーションが壊れてしまう問題に遭遇しました。 com.google.dagger:hilt-android から参照している com.square.javapoet:1.11.1 では問題ないようですが、androidx.hilt:hilt-compiler が参照している 12.1 への依存に引っ張られて問題が発生しているようです。

github.com

継承関係を持つ Fragment でそれぞれ AndroidEntryPoint を指定できない

アノテーションプロセッサで生成されるクラスを継承関係に挟み込む Hilt の仕組みが影響している問題です。 手元で発生を確認したのは Fragment ですが、AndroidEntryPoint をもつクラス同士で継承関係があれば Activity などでも発生するものだと思います。 継承関係手を加えることで似たような問題がほかにも出てきそうな予感がします。

github.com

テストで Hilt を利用する場合 @HiltAndroidTest が必須に

テスト実行時に Hilt で依存注入される値の切り替えなどを行う場合、HiltTestApplication または @CustomTestApplication を利用した Application クラスに TestApplication を切り替える必要があります。 これに加えてテストクラスに @HiltAndroidTest アノテーションをつけ、@BindValue によって注入される値の切り替えを行うことができます。

しかし Hilt を利用する TestApplication を指定した場合、@HiltAndroidTest がついていないテストクラスはすべて実行時エラーとなってしまいます。 つまり Hilt を利用していないクラスのテストクラスでも、同じ Gradle モジュールで RobolectricTest または AndroidTest として実装するものはすべてアノテーションの付与が必須となっています。

これが仕様上の限界なのか意図していないバグのようなものなのかはまだわかっていませんが、このままでは簡単に移行することは難しい印象です。 @BindValue などは非常に便利なので上手い着地点に落ち着くことを祈っています。

まとめ

アルファリリースを検証する上でいくつかの問題に遭遇しましたが、個人的には ViewModel の問題に対して良い解決策が出てくるまでは移行には早いと感じました。

また継承関係に割り込む仕様は少し強引なので、今後も問題がぽろぽろ出てくるかなと予想しています。

ただ Hilt は実装コストや学習コストを下げる上で大きな希望となってくれるものと期待しているので、今後も変更を追っていきたいと思います。

「知識ゼロから学ぶソフトウェアテスト」を読んだ

ブログ全然書いてないので、これからは本を読んだ感想くらいから頑張って書いていきたい。

最近ソフトウェアテストについてやっていきがあるので、ガツガツと本をあさっています。

まだまだ初心者なので、初心者用にオススメされた「知識ゼロから学ぶソフトウェアテスト」を読んだ感想を少し書きます。

知識ゼロから学ぶソフトウェアテスト 【改訂版】

知識ゼロから学ぶソフトウェアテスト 【改訂版】

ソフトウェアテストの基礎の基礎の部分をうまくまとめているような内容だった。

おおよその内容はすでに知っている内容でだったけど、しっかりと言語化して説明されているものを読んだことがなかったため、改めて認識を確認することができた。

いくつか気になった点や初めて知った内容があったのでまとめてみた。

探索的テスト

Cem Kaner 氏がよって提言されたもので、本の中で定義されていた内容をそのまま記すと

  • ソフトウェアテストの1つのスタイルである
  • 個人に自由意志を持たせるとともに責任をより明確にする
  • 一個人のテスト活動である
  • 継続的にテスト活動を洗練させる
  • 探索的テストには以下の活動を行う
    • テスト関連の学習
    • テスト設計
    • テスト実行
    • テスト結果を報告
  • 成熟したテスト活動
  • 上記の活動をプロジェクト期間中並行して行う

らしい。

長い、要はテストをアジャイル的にやっていくことなのかなと理解した。

探索的テストは本の中で、プロとして成熟した一個人のテスト担当者に責任をもってやらせる!という記述があったが、自分はまだ プロとして成熟した一個人 ではない。

しかし個人的には、テスト初心者がこういった探索的テストのような活動を行っていくことで、ソフトウェア自体への理解ソフトウェアテストの学習 を得ることができる効率のいい方法なのではないかと思った。

ただ企業としてそういったことに対してコストを払うことができるかとか、その一個人に対してどのくらい投資をするのかなどを考えられるような場所じゃないと厳しいんだろうなとも思った。

非機能要求

非機能要求とは、品質特性を確保するためのものであり、品質特性とは機能的な側面と非機能的な側面の両方の属性を示したものであるらしい。かなりややこしい。

本の中では、「非機能要求のテスト = 品質特性をテストすること」となっていた。要はソフトウェアの機能面のみの話ではなく、ソフトウェアとして持っているべき特性について言及しているのかなという理解をした。

著者は多様な品質特性の中でも、機密性信頼性パフォーマンス の3つが重要と言っている。 重要なのは理解できるがこれらをテストすることは非常に難しいだろ、と思ったがすぐにそういった難しさについても言及されていた。やはり経験がものを言うのだろうか…

テストの自動化

私が読んだのは2013年改定版であるが、そのころのテスト自動化ツールがあまりよくないせいか、著者はテスト自動化についてかなり否定的な内容を述べている。

最近ではGUIソフトウェアに対してのテストを自動化するツールもたくさんあり、更にそれらはOSSとして公開されているものも多い。こういった状況を考えると、 テストの自動化環境においてはかなり改善されてきているのかなという印象をうけた。

自動化に向かないテスト として回帰テストが上げられており、その話の中で著者が経験した、「自動化のコードをただ修正していくのみでテストケースは一切増えない」という話をみて、少し自分も似てような経験があったので厳しい気持ちになった。

まとめ

文字の大きさも大きく、そこまで厚い本でもないためさっくりと読破できる量であった。

冒頭にも書いたが、内容は総じてソフトウェアテストについての概要知識だった。ただ、著者が今まで経験してきた事例を通して実際にどういった点で優劣があるのかということが少し具体的に書かれていたので非常に読みやすかった。

ソフトウェアテストエンジニアというよりは、ソフトウェアテストの担当者 を対象にした本であるため、コードを書く私のような人間からすると少し物足りないかなという印象だった。

ソフトウェアテストについてざっくりとした概念を学ぶ初心者にとっては、良い入門書となる気がした。

その他

ちょくちょく格言というか迷言のようなものが書かれているが個人的には好きだった。(大半は著者の言葉)

次はこれを読む

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)

RealmObjectにLombokを使おうと思ったら死んでしまった話

久しぶりに書きます、またまたAndroidの話です。

さてネイティブアプリ自身にデータベースを持つアプリケーションは様々あると思います。

Androidでは標準でSQLiteがサポートされているのでこちらを使う方がほとんど?だと思います。 そんな中最近Realmというライブラリが注目を集めているようなので実際に使ってみました。

realm.io

※ちなみに今回の記事はRealmの紹介記事ではないので、紹介記事が見たい方は他の記事を参考にしてください。

使ってみて思ったこと

実際に使ってみて思ったこととしては簡単に使えるという印象でした。

コードを書き始めて間もなく、データベースがまったくわからないけどデータベースを使ってみたいと考えている初学者にとってはかなり使いやすく、開発スピードがとても速くなると思います。

逆に悪い点についてですが、それが今回の記事の中身だったりするので感想はこれくらいにして本題に入りたいと思います。

Lombokが使えない!!

きれいなコードを書くため冗長なコードはできるだけ避けることはみなさん意識していると思います。

Androidではそんな人達のためにLombokという素晴らしいライブラリが存在しており、精神衛生上良いコードを書くためにいつも使わせて頂いています。

projectlombok.org

※ちなみに今回はLombokの導入記事でもありません。

そしてやっと今回の本題の問題にぶつかるのですが、RealmObjectを

@Getter
public class User extends RealmObject {
    private int id;
    private String name;
}

という感じで記述するとエラーがでます。

error: Only getters and setters should be defined in model classes

このエラーを見た瞬間に「あ、察し」だったのですが調べてみると

[Feature Request] Project Lombok integration? · Issue #502 · realm/realm-java · GitHub

自分のksな英語力によるとどうやらLombokによる生成とうまく噛みあわせることができていないようです。

そもそもLombokはアノテーションを加えるだけで勝手にコードが生成されてしまうのは謎が多すぎるので少し調べてみました。

結果的にいうとLombokの機能としては

ということのようです。

まあそれだったら他のライブラリと組み合わせるのは難しいですよね...

しかし上記のissueを見てみるとどうやら開発者側もできたらいいねという感じでまだissueもopenしているので

もしかしたら今後対応するかもしれない...!

という淡い希望を抱いていきたいと思います。

参考

pixivインターンに行ってきた

8/31からの10日間、pixivのインターンに行ってきました。

f:id:ksfee:20150917173106j:plain

今回のインターンスマートフォンアプリの開発がメインで、僕の開発は主にAndroidだったりするのでAndroidエンジニアとしての参加でした。

pixivのみなさんにはこのような始めたばかりのブログに体験談を書いてしまって大変申し訳ない気持ちでいっぱいです...。

pixivの雰囲気

まずはpixivどんなかんじだった?とか言われそうなのでしっかりと伝えたいと思います。

端的にいうと良さ!という感じです。これは決して脅されているから言っているとかそういうのではなくて心から思っていることです。

具体的にどういった良さがあるかというと以下のようなランキングです。

  1. みそしる
  2. 面白くて気さくな社員の方々
  3. 開放的なオフィス

突っ込みどころは満載ですが下から見ていきたいと思います。

開放的なオフィス

ブログ用などに写真を撮ることをまるっきり忘れていて微妙な写真でしか伝えられないのが残念ですが、pixivでは社員全員の顔を見渡せるような開放的なオフィスになっていました。

それがどうしたと思う人は多々いるかと思いますが、個人的に大規模な会社で社屋が分かれていたりすると社員同士が顔を合わせる機会が減ってしまい、少し冷たい印象を受けてしまいます。 pixivでは全体見渡すことができ、団結力というか集団意識が生まれて個人的にはかなりいいつくりだなと思いました(細かい)。

f:id:ksfee:20150917170003j:plain

面白くて気さくな社員の方々

pixivの社員さんは面白い人ばかりです!!

社会人の方々とあまり面識がないせいもあるのかもしれないですが、自分の中にある社会人というイメージよりもかなり柔らかいという印象でした。

pixivというサービスのコアユーザが10,20代ということもかなり関係していると思いますがとにかく発想が若い!www。

そんな社員の方とインターン中に様々なことについてお話することができ、ただ面白かっただけではなく、今後の糧となるようなことばかりでした。人の話を聞くのはやっぱり大事ですねー。

みそしる

一人暮らしでかなり不足がちな大豆成分...。

そんな一人暮らしへ送る1杯のみそしる、すばらしいですね。

要はオフィスでみそしるが飲めたって話です、飲み過ぎて塩分かなり摂り過ぎたと思います。ちゃんと運動して汗流したいです。

なぜ参加したのか

じゃあなんで参加したのという

元々大学をでたら院進せずに速攻で就職してやろうと考えており、「いまのうちからインターン行くと面白いんじゃね?」というてきとうな考えのもとインターンを探し始めました。

そんな中とある某ようじょ先輩からpixivのインターンが非常に良いというお話をいただき詳しく調べてみることにしました。調べてみたところ以下のメリットがあることがわかりました。

  • 10日間という限られた中で1つのプロダクトをつくることができる
  • 自分はアニオタである

まず1点目に関してですが、他社のインターンを見てみるとかなりの割合で自社のサービスを実際に開発してもらうという形式が多いという印象がありました(個人の感想です)。そんな中、今回のpixivのようなインターンは珍しく、目を引くきっかけとなりました。

2点目に関しては正直もう言うことはないと思います。pixivは一種の聖地なんじゃとか考えてます。

なにしたの

今回のインターン1日x回は起動したくなるアプリ創作活動を盛り上げるという課題でのアプリ開発でした。

イデアの考え方から企画のブレスト、仕様定義、実装、ドッグフーディング、そしてプロトタイプを用いての発表会というようなアプリ開発の一連の流れをしっかりとさせていただきました!

なにか期間内でプロダクトをつくるようなインターンであると10日間よりももっと少ない期間でこの流れをかなりふっ飛ばしてのものならあると思いますが、ここまで流れをしっかりと舐めることができるのはpixivの特徴かと思います。

チームで開発したアプリは惜しくも優勝を逃してしまいましたが、他のチームと比べてクオリティは1番良かったんじゃないかと自負しています。普通プロトタイプでまでチュートリアルを用意するアホはいません。

とにかく激しい開発であったことは間違いないです。

しょうじき苦労したところ

それでもなんとかアプリの題材も決めることもでき、仕様定義を決めている時に事件は起こりました。 限られた期間内で実装できる量は有限です、そういった中でこれは本当にコアな部分なのかという話で意見が強く衝突しました。普段からチームの開発メンバーと言い合い(争いではない)をしている自分にとっては慣れていることで特に紀にしていなかったのですが、かなり自分の自己主張が強く、他のチームメンバーの意見をかなり潰してしまいました。

普段から自分から意見をがつがつぶつけていかないと生きていけない環境(決してそんなことはない)にいたので自分から相手の意見を吸い出すようなことはまったく意識していなかったので、非常に驚き、またいい経験になりました。

仕様定義で十分にぶつかったおかげか実装段階ではあまり困ったことになることはなく、至って順調な開発となりました。ただ、タスクの量ははんぱなかったと思います。

実際の開発はどうだったの

アプリの概要としては全画面で通信処理が発生するようなものだったので、いかに非同期処理を淡々と書いていくかを考えながらつくっていました。

今回のアプリでは無難にRetrofitRxJavaを使っていきました。また各モジュールで作業を分けて開発はしましたが、チームメンバーがあまり通信周りを書いたことがないとのことだったので通信処理は全て請け負い、とにかく開発のスピードを上げることを重視しました。

あとやっぱりカメラアプリは闇ですね...。

ちらっとですが今回つくったアプリ載せときます。

f:id:ksfee:20150917235401p:plain

まなびあること

今回のインターン中でのまなびとして簡単にいうと技術面のまなびはあまりありませんでした。それは当然のことのように思えるので技術的なまなびを求める人は実際の業務に入るタイプのインターンへ参加するのをおすすめします。

じゃあ何をまなんだんだという話なんですが、今回のまなびとしてはプロジェクトの立ち上げとはというものすごく意識高い分野についてが1番大きかったなと思います。

自分がいままで関わってきたプロジェクトのことを考えた際に、自分が立ち上げの段階で1から関わっているものは0でした。かなり早期の段階で入ったものはありますが大元の案はすでにあり、それを発展させていくというものばかりでした。

そういった経緯から今回のインターンでの経験は非常にためになりました。非エンジニア職である総合職とデザイナーとのやりとり、課題の内容にも沿いつつ会社としてのプロダクトである以上収益も考えなければいけないなど、もろもろの内容を全て加味しつつ、企画を1からできたというのはこれからエンジニアとして生きていくうえでかなり重要な経験であったと思います。

まとめ

かなりだらだらと書いてしまいましたが、最終的にまとめると最高の10日間だったということです!

とてつもなく文才が無いのでひどい文章になっていますが、「ここはどうだったんだよ?」的なお話はがんがん聞いて欲しいのでリアルな知り合いの方は頑張ってコンタクトを取ってください。

最後に10日間自分の業務があるにも関わらず面倒を見てくださったメンターのみなさん、またメンターとしてではなくてもアドバイスや楽しいお話をしてくださったpixiv社員のみなさんには大変感謝しています。

このような貴重な体験をさせていただきありがとうございました!!