From b484a1f8be719ef5aba2ead9aab5a4671eb91dc4 Mon Sep 17 00:00:00 2001 From: Aitor Viana Date: Fri, 3 Dec 2021 16:18:47 +0000 Subject: [PATCH] Allow AppScope -> ActivityScope -> FragmentScope dagger scope nesting --- .../app/global/DuckDuckGoActivity.kt | 4 +- di/build.gradle | 1 + .../java/dagger/android/AndroidInjector.kt | 39 ++++++++++++++++--- .../java/dagger/android/DaggerActivity.kt | 37 ++++++++++++++++++ .../support/AndroidSupportInjection.kt | 4 +- 5 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 di/src/main/java/dagger/android/DaggerActivity.kt diff --git a/common-ui/src/main/java/com/duckduckgo/app/global/DuckDuckGoActivity.kt b/common-ui/src/main/java/com/duckduckgo/app/global/DuckDuckGoActivity.kt index 6ba0ce19f0e6..a9e733fb8562 100644 --- a/common-ui/src/main/java/com/duckduckgo/app/global/DuckDuckGoActivity.kt +++ b/common-ui/src/main/java/com/duckduckgo/app/global/DuckDuckGoActivity.kt @@ -20,7 +20,6 @@ import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.os.Bundle import android.view.MenuItem -import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -29,9 +28,10 @@ import com.duckduckgo.mobile.android.R import com.duckduckgo.mobile.android.ui.applyTheme import com.duckduckgo.mobile.android.ui.store.ThemingDataStore import dagger.android.AndroidInjection +import dagger.android.DaggerActivity import javax.inject.Inject -abstract class DuckDuckGoActivity : AppCompatActivity() { +abstract class DuckDuckGoActivity : DaggerActivity() { @Inject lateinit var viewModelFactory: ViewModelFactory diff --git a/di/build.gradle b/di/build.gradle index a4ba43ad5fa3..b901425a05af 100644 --- a/di/build.gradle +++ b/di/build.gradle @@ -25,4 +25,5 @@ dependencies { implementation Kotlin.stdlib.jdk7 implementation AndroidX.fragmentKtx implementation Google.dagger + implementation AndroidX.appCompat } diff --git a/di/src/main/java/dagger/android/AndroidInjector.kt b/di/src/main/java/dagger/android/AndroidInjector.kt index 4361cdf9129c..33a0c0b3ac3f 100644 --- a/di/src/main/java/dagger/android/AndroidInjector.kt +++ b/di/src/main/java/dagger/android/AndroidInjector.kt @@ -61,13 +61,13 @@ interface AndroidInjector { * 2. Use the factory to create the dagger component that relates to an Android type, eg. Activity * 3. Inject any dependency requested by the Android type */ - inline fun inject(application: Application, instance: T, mapKey: Class<*>? = null) { - if ((application is HasDaggerInjector)) { - (application.daggerFactoryFor(mapKey ?: instance!!::class.java) as Factory) + inline fun inject(injector: Any, instance: T, mapKey: Class<*>? = null) { + if ((injector is HasDaggerInjector)) { + (injector.daggerFactoryFor(mapKey ?: instance!!::class.java) as Factory) .create(instance) .inject(instance) } else { - throw RuntimeException("Application class does not extend ${HasDaggerInjector::class.simpleName}") + throw RuntimeException("${injector.javaClass.canonicalName} class does not extend ${HasDaggerInjector::class.simpleName}") } } } @@ -87,7 +87,7 @@ class AndroidInjection { } inline fun inject(instance: T, bindingKey: Class<*>? = null) { - AndroidInjector.inject(instance.context?.applicationContext as Application, instance, bindingKey) + AndroidInjector.inject(findHasDaggerInjectorForFragment(instance), instance, bindingKey) } inline fun inject(instance: T, bindingKey: Class<*>? = null) { @@ -97,5 +97,34 @@ class AndroidInjection { inline fun inject(instance: T, context: Context, bindingKey: Class<*>? = null) { AndroidInjector.inject(context.applicationContext as Application, instance, bindingKey) } + + /** + * Injects the [fragment] if an associated [AndroidInjector] implementation is found, otherwise [IllegalArgumentException] + * is thrown. + * + * The algorithm is the following: + * * walks the parent fragment hierarchy until if finds one that implements [HasDaggerInjector], else + * * uses the [fragment]'s and returns it if it implements [HasDaggerInjector], else + * * uses the [Application] and returns it if it implements [HasDaggerInjector], else + * * throws [IllegalArgumentException] + */ + fun findHasDaggerInjectorForFragment(fragment: Fragment): HasDaggerInjector { + var parentFragment: Fragment? = fragment + while (parentFragment?.parentFragment != null) { + parentFragment = parentFragment.parentFragment + + if (parentFragment is HasDaggerInjector) { + return parentFragment + } + } + val activity = fragment.activity + if (activity is HasDaggerInjector) { + return activity + } + + activity?.application?.let { return it as HasDaggerInjector } + + throw IllegalArgumentException("No injector found for ${fragment.javaClass.canonicalName}") + } } } diff --git a/di/src/main/java/dagger/android/DaggerActivity.kt b/di/src/main/java/dagger/android/DaggerActivity.kt new file mode 100644 index 000000000000..029b51f22989 --- /dev/null +++ b/di/src/main/java/dagger/android/DaggerActivity.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.android + +import androidx.appcompat.app.AppCompatActivity +import com.duckduckgo.di.DaggerMap +import javax.inject.Inject + +abstract class DaggerActivity : AppCompatActivity(), HasDaggerInjector { + @Inject + lateinit var injectorFactoryMap: DaggerMap, AndroidInjector.Factory<*>> + + override fun daggerFactoryFor(key: Class<*>): AndroidInjector.Factory<*> { + return injectorFactoryMap[key] + ?: throw RuntimeException( + """ + Could not find the dagger component for ${key.simpleName}. + You probably forgot to create the ${key.simpleName}Component. + If you DID create the ${key.simpleName}Component, check that it uses @ContributesTo(ActivityScope::class) + """.trimIndent() + ) + } +} diff --git a/di/src/main/java/dagger/android/support/AndroidSupportInjection.kt b/di/src/main/java/dagger/android/support/AndroidSupportInjection.kt index abb2a958bc2a..e87cac40ea31 100644 --- a/di/src/main/java/dagger/android/support/AndroidSupportInjection.kt +++ b/di/src/main/java/dagger/android/support/AndroidSupportInjection.kt @@ -16,14 +16,14 @@ package dagger.android.support -import android.app.Application import androidx.fragment.app.Fragment +import dagger.android.AndroidInjection import dagger.android.AndroidInjector class AndroidSupportInjection { companion object { inline fun inject(instance: T) { - AndroidInjector.inject(instance.context?.applicationContext as Application, instance) + AndroidInjector.inject(AndroidInjection.findHasDaggerInjectorForFragment(instance), instance) } } }