Be simple

”当たり前”が誰かのためになる

Dagger2を使ってビルド環境によって処理を分ける手法について

はじめに

この記事は、Android #2 Advent Calendar 2018 - Qiita 24日目の記事です。

 

はじめましての方は、はじめまして。知っている方は、お久しぶりです。

ウイスキーと筋肉を愛するAndroidエンジニア、@zukkeyです。
 

f:id:rozkey59:20181224131440j:plain

イメージ



今回はDIライブラリDagger2を利用し、ビルド環境によって処理を分けるための手法について紹介したいと思います。

 

今回書いてること
  • Dagger2とは
  • Dagger2の使い方
  • Debug環境とRelease環境で処理を分ける

 

対象とする読者
  • ビルド環境ごとの処理をDagger2で分けたくて仕方がない人
  • Kotlinを習得している方
  • Dagger2を多少知っている方 or はじめてみたい方

 

今回書かないこと
  • Daggerの詳しい説明などは省きます

 

それでは順にやっていきましょうー

 

Dagger2とは

AndroidとJavaのための依存性注入ライブラリです。

GitHubのリンクは次のリンクから飛べます。

github.com

依存性注入ライブラリは様々ありますが、Googleがメンテナンスしていてかつ依存性注入に面倒な部分をやってくれる現状のデファクトスタンダートなライブラリになります。

Dagger2や依存性注入については、先人がわかりやすい記事を書いていらっしゃるので次のリンクをみると良いです。

 

yuki312.blogspot.com

 

qiita.com

 

また、公式の講演もあるので字幕をつけて1.5倍速とかでみるとさらっと背景から知ることができます。

www.youtube.com

 

Dagger2の使い方

app配下のbuild.gradleにて、次のコードを記載します。

 

apply plugin: 'kotlin-kapt'

dependencies {
// Dagger
implementation 'com.google.dagger:dagger:2.20'
kapt 'com.google.dagger:dagger-compiler:2.20'
}

2018年12月現在、Daggerの最新バージョンは2.20です。

Kotlinを利用している場合はkaptで指定しましょう。

 

Dagger2でDIを行うには、Module、Component、SubComponentについて知っておく必要があります。

こちらについては、すでにQiitaにて先人がまとめているので、次のリンクを参照してください。

 

qiita.com

 

では、本題に入ります。

 

Debug環境とRelease環境で処理を分ける

 debug環境とrelease環境によって処理を一部分けたい時が出てくると思います。

debug環境では、開発効率のための機能を提供するが、release環境ではその処理を記載したくない場合などに役立ちます。

 これを実現するために、今回は例としてdebug環境にシェイク機能をつけるが、release環境にシェイク機能をつけないようにするということをDagger2を用いて実現しようと思います。

 順をおって次の通りに説明していきます。

  • シェイク機能の導入
  • サンプルアプリの階層構造
  • 実際にサンプルを実装してみる

 

シェイク機能の導入

  シェイク機能をつけるにあたって、次のライブラリを利用します。

github.com

 

 例によって、app配下のbuild.gradleに次のコードを記載します。

dependencies {
// Shake
debugImplementation 'com.squareup:seismic:1.0.2'
}

 debug環境だけライブラリを導入したいので、debugImplementationで指定します。

 2018年12月現在、最新バージョンは1.0.2です。

 

サンプルアプリの階層構造

実装について説明する前に、サンプルアプリの階層構造について先に説明します。

- app

 - src

  - debug

  - main

  - release

 app配下にてmain、debug、releaseに階層を分けます。

main配下には、debugとreleaseで共通した処理を持たせます。

debug配下には、debug環境のみで動く処理を、release配下には、release環境のみで動く処理を持たせます。

 

今回シェイク機能をdebug環境のみに搭載させるにあたって、ShakeHandlerのインターフェースをmainに記載して、ShakeHandlerを実装するクラスをdebugにはDebugShakeHandler、releaseにはReleaseShakeHandlerを作成します。

 

DebugShakeHandlerクラスでは、端末をシェイクしたのを感知したらトーストを表示するようにし、ReleaseShakeHandlerクラスでは、何もしないようにします。

 

実際にサンプルを実装してみる

AppModuleとAppComponentがある前提で話します。

 

最初に、ShakeHandlerインターフェースを作成していきます。

main配下に次のInterfaceを作成します。

interface ShakeHandler {
fun onCreate()
fun onDestroy()
}

 

次に、debug環境の実装を書いていきましょう。

まずdebug配下にて、DebugShakeHandlerクラスを作成します。

class DebugShakeHandler(
private val activity: AppCompatActivity
): ShakeHandler, ShakeDetector.Listener {

override fun onCreate() {
val sensorManager = activity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val shakeDetector = ShakeDetector(this)
shakeDetector.start(sensorManager)
}

override fun hearShake() {
Toast.makeText(activity, "Shaked!", Toast.LENGTH_SHORT).show()
}

override fun onDestroy() {
// do nothing
}
}

ShakeDetectorによって端末を振っているかどうかの検知の開始をonCreateで定義しています。

ShakeDetecterのCallbackがhearShakeにくるので、シェイク動作を検知した後にトーストを表示するようにしています。

 

次に、DebugShakeHandlerを提供するためのHandlerModuleを作成しましょう。

HandlerModuleは次のように作成します。

@Module
class HandlerModule(
private val activity: AppCompatActivity
) {

@Provides
fun provideShakeHandler(): ShakeHandler {
return DebugShakeHandler(activity)
}
}

@Providesメソッドで注入したいものを生成します。

今回は、DebugShakeHandlerを注入したいので、上のように定義しました。

 

ここまでできたら、次は、release配下にて、ReleaseShakeHandlerクラスを作成します。実際の実装は次のようになります。

 

class ReleaseShakeHandler(
private val activity: AppCompatActivity
) : ShakeHandler {
override fun onCreate() = Unit

override fun onDestroy() = Unit
}

ReleaseShakeHandlerでは何もしないのでUnitを返すようにしています。

 

次に、ReleaseShakeHandlerを提供するためのHandlerModuleを作成しましょう。

HandlerModuleは次のようになります。

@Module
class HandlerModule(
private val activity: AppCompatActivity
) {

@Provides fun provideShakeHandler(): ShakeHandler {
return ReleaseShakeHandler(activity)
}
}

 先ほどのHandlerModuleと違うのは、ShakeHandlerとして注入物を生成していますが、中身は別のものになっているということです。

 

次は、main配下にてAppComponentのSubComponentとして、HandlerComponentを作成していきます。実際の実装は次のようになります。

 

@Subcomponent(modules = [HandlerModule::class])
interface HandlerComponent {
fun inject(activity: MainActivity)
}

HandlerModuleをAppComponentのSubComponentとして定義したら、AppComponentを次のようにします。

@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(application: SampleApp) // ApplicationクラスにInject
fun plus(module: HandlerModule): HandlerComponent
}

 ここまでできたら、HandlerModuleをMainActivityにInjectして依存性注入をしましょう。

MainActivityは、次のように変更します。

class MainActivity : AppCompatActivity() {
@Inject lateinit var shakeHandler: ShakeHandler

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = (application as SampleApp).component
component.plus(HandlerModule(this)).inject(this)
shakeHandler.onCreate()
}

override fun onDestroy() {
super.onDestroy()
shakeHandler.onDestroy()
}
}

HandlerModuleを渡して、Injectし、onCreateとonDestroyでshakeHandlerを利用します。

 

ここまで実装することで、Build Variantをdebugとreleaseに切り替えるだけで処理をかえることができます。

実際にサンプルを動かしてみた結果が次のようになります。

 

f:id:rozkey59:20181224130025g:plain

debug環境実行結果

debugビルドだと、端末を振るとトーストが表示されます。Shaked!という文字が表示されていることを確認できます。

 

f:id:rozkey59:20181224130102g:plain

release環境実行結果

画面だと一切わからないのですが、めちゃくちゃ端末を振ってますが何も表示されていません。

 

おわりに

無事、Daggerを用いることで、依存注入物の処理を分けるだけでdebug時とrelease時の処理を分けることが可能になりました。

 

全体のサンプルコードについてみたい方は、次のリンクから参照してください。

 

github.com

 

駆け足の説明になってしまいましたが、不足部分や間違っているところなどあればコメントにてご指摘いただけると幸いです。

最後まで見ていただきありがとうございました。それではまた次の機会に。