dagger-androidでDialogFragmentにInjectする

仕事で担当しているプロダクトではDI用のライブラリとしてDagger2のAndroid拡張(https://google.github.io/dagger/android)を使用しています。 先日DialogFragmentに対してDIを行う処理を書いたのですが、その際のやり方をまとめておきます。

Scopeの定義

このプロジェクトではDaggerのScopeとしてActivityScopeFragmentScopeのようなカスタムScopeを定義しています。

同じようにDialogFragment用のScopeとしてDialogFragmentScopeを定義します。

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface DialogFragmentScope {
} 

Component, Module定義

dagger-androidでは@ContributesAndroidInjectorアノテーションを用いてActivity, Fragmentなどのコンポーネント定義を行うことができます。 また、@ContributesAndroidInjectorアノテーションにmoduleパラメータを指定し、該当モジュール内で更に@ContributesAndroidInjectorの定義を行う事で、サブコンポーネントの定義を行うことができます。

これを利用して今回は以下のようなコンポーネント関係を構築します。

ActivityComponent → FragmentComponent → DialogFragmentComopnent

  • Application用のModule(Activity用のInjectorを定義)
@Module
@Suppress("unused")
interface ApplicationModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = [MyActivityModule::class])
    fun contributeMyActivity(): MyActivity
}
  • Activity用のModule(Fragment用のInjectorを定義)
@Module
@Suppress("unused")
interface MyActivityModule {
    @FragmentScope
    @ContributesAndroidInjector(modules = [MyFragmentModule::class])
    fun contributeMyFragment(): MyFragment
}
  • Fragment用のModule(DialogFragment用のInjectorを定義)
@Module
@Suppress("unused")
interface MyFragmentModule {
    @DialogFragmentScope
    @ContributesAndroidInjector
    fun contributeMyDialogFragment(): MyDialogFragment
} 

Inject

DialogFragment側の実装ではDaggerAppCompatDialogFragmentもしくはDaggerDialogFragmentを利用します。 あとは普通に@Injectアノテーションを用いて使用したいクラスを注入すればOKです。

class MyDialogFragment : DaggerAppCompatDialogFragment() {
    @Inject
    lateinit var presenter: MyPresenter
}

ここでInjectされているMyPresenterがFragmentScopeやActivityScopeとなっている場合、上位のFragment/Activityとインスタンスが共有されますのでPresenter経由でFragmentを操作する事などが可能になります。

AACのViewModelをKoinでInjectする

Kotlinを利用したプロジェクトで利用できるDIライブラリであるKoin(https://github.com/InsertKoinIO/koin) ですが、Android開発用の拡張ライブラリであるkoin-androidを使ってAndroid Architecture ComponentsのViewModelを生成する方法についてまとめます。

基本的には下記のサイトに記載されている内容です。 https://beta.insert-koin.io/docs/1.0/quick-references/koin-android/#android-architecture-viewmodel

Koinの基本的な使い方については上記githubのREADME等をご参照ください。

Koinを用いない場合のViewModelの生成

Koinを用いない場合のAndroid Architecture ComponentsのViewModelクラスの生成方法は次のようにViewModelProvidersを使用します。

public class MyActivity extends AppCompatActivity {
    public fun onCreate(savedInstanceState: Bundle) {
        val model = ViewModelProviders.of(this).get(MyViewModel::class.java);
    }
}

参考:https://developer.android.com/topic/libraries/architecture/viewmodel

Koinを用いる場合のViewModelの生成

Koinを用いる場合は以下のようにby viewModel()によりインスタンス生成を行います。 通常のクラスの場合はby inject()としますが、ViewModelの場合はby viewModel()を指定してください。

class LoginActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

また、プロパティなどにインジェクションしない場合はgetViewModel()関数を使用することも可能です。

override fun onCreate() {
    super.onCreate()

    val model : MyViewModel = getViewModel()
}

ViewModelの為のモジュール定義

この場合のViewModel生成の為のモジュール定義は通常の場合のsinglefactoryではなく次のようにviewModelを指定します。

val viewModelModule = module {
    viewModel<MyViewModel>()
    viewModel { ParameterNeedViewModel(get()) } // パラメータが必要な場合
}

Koinでインスタンス生成時にActivityをinjectする方法

Koinを使ったをDI行った際に、Activity/Fragmentにinjectしたクラス(Presenterなど)に対してActivity/Fragmentなどをコンストラクタインジェクションする方法を調べました。

対象となるクラスの定義

今回のサンプルとしてコンストラクタパラメータとしてActivityおよびUseCaseを必要とするPresenterを定義します。 (UseCaseクラスのインスタンス生成に関しては別途モジュール定義がされているものとします)

class LoginPresenter(private val activity: Activity, private val loginUseCase: LoginUseCase) {
    …
}

モジュールの定義

上記のLoginPresenterを生成するためのモジュール定義は以下のようになります。
通常のモジュール定義と異なり、LoginUseCaseに関してはget()を用いたKoinによるインスタンス生成を行いますが、Activityに関してはfactoryブロックのパラメータとして定義します。

val presenterModule = module {
    factory { (activity: Activity) -> LoginPresenter(activity, get()) }
}

Activity側の実装

次のようにInjectする際にパラメータとしてfactoryブロックのパラメータとして定義したActivityインスタンス(this)を指定する事が可能です。

class LoginActivity : AppCompatActivity() {
    private val loginPresenter: LoginPresenter by inject { parametersOf(this) }

    override fun onCreate(savedInstanceState: Bundle?) {
        …
    }
    
    …
}

参考

https://beta.insert-koin.io/docs/1.0/documentation/koin-core/index.html#_defining_an_injection_parameter

Firebase Authenticationの実装(Android, Google認証)

Firebase Authenticationの実装を行ったので実装内容をまとめておきます。

この記事では以下の内容についてまとめています。

  • Android
  • Kotlinで実装
  • Googleアカウントを用いた認証

基本的には公式ドキュメント(https://firebase.google.com/docs/auth/android/google-signin)の内容通りの実装です。

Googleログインの統合まわりで別のページ(https://developers.google.com/identity/sign-in/android/sign-in)を案内されるのが少し分かりにくかったので自分用にまとめ直しています。
(ちょっとガーっと書いちゃったのであとで見直します。見直すべき。見直したい人生だった。)

build.gradleへの追記内容

Firebase Authentication用のモジュールとGoogle Sign-In用のモジュール(Google Play Service)が必要となるので 以下のようになります。

apply plugin: 'com.google.gms.google-services'

android {
    …
}

dependencies {
    // Google Sign-In
    implementation 'com.google.android.gms:play-services-auth:16.0.0'

    // Firebase Authentication
    implementation 'com.google.firebase:firebase-core:16.0.3'
    implementation 'com.google.firebase:firebase-auth:16.0.3'

Firebaseのプロジェクト側での設定とgoogle-services.jsonの生成

こちらは公式ドキュメントの「準備」に手順が書いてあるので詳しくはそちらを参照してください。 Firebase Consoleにアクセスし、対象となるFirebaseプロジェクト(なければ新規作成する)にAndroidアプリを追加(パッケージ名の登録や、key storeのフィンガープリントの登録が必要)を行うとgoogle-service.jsonファイルがダウンロード可能になるので、そちらをアプリに配置します。

現在の認証状況を確認する

通常アプリで認証を要求する前に、現在の認証状況を確認し、認証済みの場合は通常時の処理、未認証の場合はログイン画面などへの遷移を行うかと思います。 Firebase Authenticationを用いて認証状況を確認する場合はFirebaseAuthオブジェクトのcurrentUserプロパティを取得する事で、現在認証済みの状態であるか判断できます。 currentUserプロパティはFirebaseUserクラスのインスタンスとなっており、ユーザー名などの情報を取得することも可能です。

なお、こちらはFirebase Authenticationの状態が認証状態にあるか否かを確認するため、Googleアカウントによる認証かどうかは問いません。

val firebaseAuth = FirebaseAuth.getInstance()

if (firebaseAuth.currentUser != null) {
    // 認証済み
} else {
    // 未認証
}

Google Sign-Inを要求する

Googleアカウントを用いてFirebase Authenticationを行う場合

  1. Google Sign-Inを使用してGoogleアカウントの認証を行い、Credentialを取得する
  2. 取得したCredentialをFirebaseに送信し、Firebase Authenticationを行う。

となります。その為のGoogle Sign-In要求のコードは以下のようなものになります。
(R.string.default_web_client_idgoogle-services.jsonの配置とbuild.gradleの記述が間違っていなければ自動生成されているはずです)

val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build()

val googleSignInClient = GoogleSignIn.getClient(this, gso)

startActivityForResult(googleSignInClient.signInIntent, RC_SIGN_IN)

Google Sign-In結果の取得

Google Sign-Inの結果はActivityResult()を通じて取得できます。 取得したGoogle Sign-Inの結果を用いてFirebaseAuth. signInWithCredential()の呼び出しを行うことで、Firebase Authenticationに対してGoogleアカウントでの認証を要求します。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == RC_SIGN_IN && data != null) {
        try {
            // Google Sign In was successful, authenticate with Firebase
            val account = GoogleSignIn.getSignedInAccountFromIntent(data)
                .getResult(ApiException::class.java)
            
            val credential = GoogleAuthProvider.getCredential(account.idToken, null)

            firebaseAuth.signInWithCredential(credential)
                .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // TODO Firebase Authentication成功時の処理
                } else {
                    // TODO Firebase Authentication失敗の処理
                }
            }
        } catch (e: ApiException) {
            // TODO Google Sign-In失敗時の処理
        }
    } else {
        super.onActivityResult(requestCode, resultCode, data)
    }
}

最近のカンファレンス資料まとめ(更新中)

先週、今週とTwitterのTL上で大規模カンファレンスに参加した方の学びの様子が観測され、地方在住を理由に行かなかった私としてはやや焦燥感を感じます。 ということで、後で見る用のカンファレンス資料まとめです(自分用)

とりあえず、Android, Flutter, Firebaseあたりは目を通したいところ(見たら印つけていく)

Kotlin Fest

connpassのページ https://kotlin.connpass.com/event/91666/

connpassのイベント資料一覧 https://kotlin.connpass.com/event/91666/presentation/

Kotlin で改善する Android アプリの品質

あんざいゆき
🧐https://speakerdeck.com/yanzm/kotlinfest

Kotlinもう一歩

森洋之
🧐https://speakerdeck.com/kobitokaba/kotlinmou-bu

Kotlinアプリのリファクタリングポイント

中里直人
https://www.slideshare.net/RecruitLifestyle/refactoring-point-of-kotlin-application

How to Test Server-side Kotlin

鈴木 健太 ・ 前原 秀徳
https://speakerdeck.com/maeharin/how-to-test-server-side-kotlin-number-kotlinfest

Kotlin linter

釘宮 愼之介
https://speakerdeck.com/kgmyshin/kotlin-linter

start from Convert to Kotlin

望月美帆
https://speakerdeck.com/mochico/start-from-convert-to-kotlin

Kotlinで愛でるMicroservices

stormcat24
https://speakerdeck.com/stormcat24/kotlin-fest

Kotlin コルーチンを理解しよう

八木俊広
https://speakerdeck.com/sys1yagi/kotlin-korutinwo-li-jie-siyou

How to Kontribute (v4 JP)

磯貝佳典
https://photos.google.com/share/AF1QipN7aY_JLG8QWBF80g-7-fisMlfGf-mQUtz81qFSlmh5DfdFWGmsaCSe2Ho0Ioc3ng?key=bURDdXBYTUdDdlVDNWYtTDhBRzFjbnQ5LVlZRjlR

iOSDC

タイムテーブル https://fortee.jp/iosdc-japan-2018/timetable

winterwind26さんのまとめ https://qiita.com/winterwind26/items/210e5735d2ce832d0c36

Google DevFest Tokyo 2018

connpassのページ https://gdg-tokyo.connpass.com/event/95307

connpassのイベント資料一覧 https://gdg-tokyo.connpass.com/event/95307/presentation/

りんごさんのまとめ https://mstssk.hatenablog.com/entry/2018/09/01/141029

Android

AndroidX時代のHello world

深見浩和 / fkm

TensorFlow Liteで機械学習Androidアプリを超簡単に作る

古川新

https://speakerdeck.com/ornew/tensorflow-litedeji-jie-xue-xi-androidapuriwochao-jian-dan-nizuo-ru

Androidパネルディスカッション: AAC実践導入について

"magie_pooh teshi04 shaunkawano 飯島彩輝 松山 純也"

新しいMaterial Design と MDC(仮)

"namiki joi yanzm"

Android OSは安全なのか?

タニグチガク

KotlinでFlutterを書けないか色々試してみた(仮

菊池 紘
https://docs.google.com/presentation/d/1Ra8hVovwTwH8kx1Q-QRVb1E9aHhGvADmgXEVSmeBlvM/edit#slide=id.p

AndroidThings で CO2 を計測し、警告&サーバーに計測値を投げるシステム

小林 慧

Advanced Room

荒木 佑一

GtugGirlsがヤバいんです長尾ユリコ

🧐https://speakerdeck.com/nagaoyuriko/gtuggirlsgayabaindesu

Web

(仮)実践!!Web パフォーマンス改善! 宇都宮 佑亮

Angularの最新情報

laco
https://slides.com/laco/devfest-tokyo-2018-angular/#/

TypeScript導入で得られる「変えていく勇気」

okunokentaro

PWAのイマ

takanorip

Introduction of Polymer 3.0

kazuyoshi kawakami

非中央集権ウェブ

aggre

Firebase

Googleアシスタント最新動向 田中 洋一郎
https://speakerdeck.com/yoichiro/google-assistant-latest-status

Game Development for Firebase Unity SDK

gremito
https://speakerdeck.com/gremito/firebasewoshi-tutegemukai-fa-woyatutemita-gdg-devfest-tokyo-2018

DialogflowとCloud Functions で作る Google アシスタント アクション

flatfisher
https://speakerdeck.com/flatfisher/dialogflowtocloud-functions-dezuo-ru-google-asisutanto-akusiyon?slide=73

Firebase Realtime Database in Production

青野 健利(ブルーノ)

Firebase Overview for native application

Daiki Matsudate

Transactions APIを使って飲食店の予約が出来るGoogle Assistantアクションが出来るまで

小林 大介
https://speakerdeck.com/covayashi/transactions-api

Firestore Database Design

1amageek

Realtime Database for high traffic production application

sota1235
https://speakerdeck.com/sota1235/realtime-database-for-high-traffic-production-application-1

Growing your app with Firebase

shihochan
"Fan out" to create twitter like timeline with Cloud Firestore and Cloud Functions タイラー

Flutter

Flutter Overview

Rui Kowase

Flutterアニメーションの実装方法

konifar

Flutterとの1年

najeira

FlutterPluginの作り方

kuwapp
https://speakerdeck.com/kuwapp/flutterpluginfalsezuo-rifang

ML

Google AIY Vision kitで遊ぼう ~麻雀牌のリアルタイム識別~
Rio Kurihara
https://www.slideshare.net/Rio/gdg2018-vision-kit-mahjong

機械学習、どこから手をつける?

上総 虎智

Kubeflowで何ができて何ができないのか?

上田 隼也
https://www.slideshare.net/shunyaueta/kubeflow-devfest18

Generative Modeling in the Wild

Rishabh Gupta

GCP

Container

Kuma Arakawa

Quick Start GCP

山内 沙織

Cloud Kata

sinmetal

GCP のデータベース・ストレージ

apstndb

GCPでつくるデータ処理パイプライン

永井 洋一

Auto ML Overview

永井 洋一

GCP Compute 概要と選定

vvakame

Go

Goらしいコードを業務でも書くために(仮)

tom

Goでネットワークプログラミングするためのよもやま話

竹澤 友博
https://speakerdeck.com/ttakezawa/talk-for-network-programming-with-go

Resources.getSystem()知らんかった

私、Android開発に7年くらい携わっているんですが、さっきResources.getSystem()というのでContextが取得できるのを知りました。 Android開発やってるとContextの取り回しに苦労する事が多いので、楽が出来るのかもなと思いました。

リファレンスによると

Return a global shared Resources object that provides access to only system resources (no application resources), and is not configured for the current screen (can not use dimension units, does not change based on orientation, etc).

https://developer.android.com/reference/android/content/res/Resources.html#getSystem()

と説明されているのでandroid.Rで参照できるリソースはOKだけど、アプリ内で定義したリソースは取得できない他、いろいろ出来ることも限られているということかな。 まぁ、覚えとくとそのうち役に立つかもなー。

マルチモジュール構成のAndroidプロジェクトでbuild.gradleの記述を共通化する

モジュール毎のbuild.gradleのdependenciesの記述を共通化したい

Android Studioでレイヤー毎にモジュール化を行ったプロジェクトを作成した際に、モジュール間の依存性解決にKoinを用いたところ、各モジュールのdependenciesにそれぞれKoinに対するimplementationを記載しなければいけなくなった。

dependencies {
    // Koin
    implementation "org.koin:koin-core:$rootProject.koin_version"
    implementation "org.koin:koin-android:$rootProject.koin_version"
}

これはウザい。 なにか良い方法が無いかなと調べたところ、プロジェクトレベルのbuild.gradleにsubprojectsブロックを定義するとすべてのモジュールで共通となる記述をまとめることが出来るようなので、試してみました。

ところがimplementationが定義されていないよ的なエラーがでてGradle Syncが失敗する…

原因調べたところ、appモジュールやライブラリモジュール作成時に定義されている

apply plugin: 'com.android.application'

などの後に定義する必要があるようです。たしかにそうですね。

解決方法

上記のapply pluginの部分をsubprojectsに移動したのですが、1つ困った事があり、

モジュールのタイプ プラグイン
App Moduleの場合 com.android.application
Android Library Moduleの場合 com.android.library

みたいな感じでプラグイン名を変える必要があったのでモジュール名によって判定するコードを追加した。

subprojects {
    switch (project.name) {
        case 'app':
            apply plugin: 'com.android.application'
            break
        default:
            apply plugin: 'com.android.library'
    }

    dependencies {
        // Koin
        implementation "org.koin:koin-core:$rootProject.koin_version"
        implementation "org.koin:koin-android:$rootProject.koin_version"
    }
}

Java LibraryやDynamic Feature Module等を使用している場合はまた別のプラグインとなるので、モジュール名によって判定するか、モジュール名の命名規則を決めてswitch文で正規表現を用いた判定などを行う必要がある。

最初のswitch文だけ書いておけば、通常のモジュール側のbuild.gradleと同じ機能がほぼ使えるので、 androidブロックの内容などもsubprojectsで共通化する事が出来るのでapp, library側含めてかなりスッキリさせる事が出来そう。