Dagger 2 - How to establish sibling dependency via Subcomponent or Component Dependency - dagger-2

Background
As per docs dependency between sibling subcomponents is not possible so, the other way left is Component Dependency. I'm trying to encapsulate "Repository" class so that UI can only access the instance of "Repository", not implementation details of "Repository". I mean nothing from ApiModule should be exposed.
ApiModule
#Module
class ApiModule {
#Provides
fun provideConnection(): Connection = //....
}
RepositoryModule
#Module (
includes = [
ApiModule::class
]
)
abstract class RepositoryModule {
#Provides
fun providesRepository(connection Connection): Repository = //.....
}
AppComponent
#Component (modules = [
AppModule::class,
RepositoryModule::class
])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
Problem
Now, if I create a subcomponent (let's say ActivitySubComponent) of AppComponent, all binding in RepositoryModule and ApiModule modules will be exposed to that subcomponent. Though, I want only provided "Repository" to be accessible from ActivitySubComponent
Proposed Solution
Create RepositoryComponenet and ActivityComponent components make AppComponenet their dependency:
AppComponent
#Component (modules = [
AppModule::class
])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
ActivityComponent
#Component(dependencies = [AppComponent::class], modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(mainActivity: MainActivity)
#Component.Builder
interface Builder{
fun appComponent(appComponent: AppComponent): Builder
fun build(): ActivityComponent
}
}
RepositoryComponent
#Component(dependencies = [AppComponent::class], modules = [RepositoryModule::class])
interface RepositoryComponent {
}
The problem is how do I expose "Repository" instance into sibling ActivityComponent's bindings or #Inject into MainActivity? Any explanation, proposal to change my configuration or links to articles that may potentially solve my problem is appreciated.

If I understand your problem correctly, you don't want to expose all dependencies from AppComponent to its child components. As component documentation says (section Component dependencies),
Note that only the bindings exposed as provision methods are available through component dependencies.
you should use Component dependencies for this purpose.
Working example is below
class Connection
interface Repository
#Module
object AppModule {
#Provides
#Singleton
fun provideConnection() = Connection()
}
#Module(includes = [AppModule::class])
object RepositoryModule {
#Provides
#Singleton
fun provideRepository(connection: Connection) = object : Repository {}
}
#Singleton
#Component(modules = [AppModule::class, RepositoryModule::class])
interface AppComponent {
val repository: Repository
}
#Scope
#MustBeDocumented
#kotlin.annotation.Retention
annotation class ActivityScope
#ActivityScope
#Component(dependencies = [AppComponent::class])
interface ActivityComponent {
fun inject(activity: FakeActivity)
}
class FakeActivity {
#Inject
lateinit var repository: Repository
// uncomment this and code won't compile because connection isn't explicitly exposed in AppComponent
// #Inject
// lateinit var connection: Connection
}
fun main() {
val appComponent = DaggerAppComponent.create()
val activityComponent = DaggerActivityComponent.builder().appComponent(appComponent).build()
val fakeActivity = FakeActivity()
activityComponent.inject(fakeActivity)
println("hash: ${fakeActivity.repository}")
println("hash: ${appComponent.repository}")
}
I used scopes because I suppose you want just one instance of Repository per Application. If something is not clear, let me know.

Related

Combining dependency injection with service locator

In the following example, a service locator is used to inject dependencies over classes. Is that in any way better approach than resolving dependencies within the class and keep the initialiser (or class interface) clean?
class MyClass {
let service: Service
init(service: Service) {
self.service = service
}
}
class RootClass {
func something() {
let myClass = MyClass(service: ServiceLocator.shared.resolve())
}
}
This is one of the way to register and inject
// Register a class
locator.registerFactory(() => Service());
class Service {}
class MyClass {
final Service service;
MyClass({this.service});
}
class RootClass {
void something() {
final myClass = MyClass(service: locator.get());
}
}

Adding Multiple Adapter Into Fragment

Hello all can you advice me how to add multiple different adapter in one fragment using dagger ?
#Module
class HomeFragmentModule {
#Provides
internal fun provideHomeInteractor(interactor: HomeInteractor): HomeMVPInteractor = interactor
#Provides
internal fun provideRegisterPresenter(presenter: HomePresenter<HomeView, HomeMVPInteractor>) : HomeMVPPresenter<HomeView, HomeMVPInteractor> = presenter
#Provides
#Named("ppobAdapter")
fun providePpobAdapter(): PpobAdapter = PpobAdapter(ArrayList())
#Provides
#Named("sliderAdapter")
internal fun provideSliderAdapter(): SliderAdapter = SliderAdapter(ArrayList())
} ```
Try this:
HomeFragment : Fragment() {
#Inject
#Named("ppobAdapter")
lateinit var ppobAdapter: PpobAdapter
#Inject
#Name("sliderAdapter")
lateinit var sliderAdapter: SliderAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
DaggerAppComponent
.builder()
.build()
.inject(this)
}
}
With a component that looks like this:
#Component(modules = [HomeFragmentModule::class])
interface AppComponent {
fun inject(fragment: HomeFragment)
}
For a really good intro to dagger I always advise this tutorial series

Dagger can not inject ViewModel with KClass

I am moving my project java to kotlin, but got some confusion about KClass and Class
Here is my BaseActivity
abstract class BaseActivity<DB : ViewDataBinding, VM : BaseViewModel> : DaggerAppCompatActivity() {
private lateinit var mCustomDialog: CustomDialog
private lateinit var mViewDataBinding: DB
private lateinit var mViewModel : VM
#Inject
lateinit var viewModelFactory: ViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set Custom Dialog
mCustomDialog = CustomDialog(this, R.style.LoadingDialogStyle)
// Set ViewModel
mViewModel = ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)
// Set DataBinding
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId())
mViewDataBinding.lifecycleOwner = this
mViewDataBinding.setVariable(getBindingVariable(), mViewModel)
mViewDataBinding.executePendingBindings()
// Initialize UI
prepareView(savedInstanceState)
}
#LayoutRes
abstract fun getLayoutId(): Int
protected abstract fun getViewModelClass(): KClass<VM>
abstract fun getBindingVariable(): Int
fun getViewModel(): VM {
return mViewModel
}
fun getViewDataBinding() : DB {
return mViewDataBinding
}
I am using protected abstract fun getViewModelClass(): KClass<VM> function for initializing ViewModel class in the function below
ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)
I use ViewModel in activities by this way
class SplashActivity : BaseActivity<ActivitySplashBinding, SplashViewModel>() {
override fun getViewModelClass(): KClass<SplashViewModel> {
return SplashViewModel::class
}
override fun getLayoutId(): Int {
return R.layout.activity_splash
}
override fun getBindingVariable(): Int {
return BR.vm
}
override fun prepareView(savedInstanceState: Bundle?) {
getViewModel().testLog()
}
}
But when I run the project, I got this error
error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.example.example.MyApp> {
^
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.example.example.utils.ViewModelFactory(viewModels)
com.example.example.utils.ViewModelFactory is injected at
com.example.example.base.BaseActivity.viewModelFactory
com.example.example.ui.splash.SplashActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.example.example.di.AppComponent ? com.example.example.di.ActivityBindingsModule_SplashActivityInjector$app_debug.SplashActivitySubcomponent]
So I made some research and find out it is about KClass in my ViewModelKey
Here is ViewModelKey
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
If I do not change my code to Kotlin and use old Java class like this it works properly
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
public #interface ViewModelKey {
Class<? extends ViewModel> value();
}
This is my ViewModelFactory class
#Suppress("UNCHECKED_CAST")
class ViewModelFactory #Inject
constructor(private val viewModels: MutableMap<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModels[modelClass]
?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
My SplashActivityModule
#Module
abstract class SplashActivityModule {
#Binds
#IntoMap
#ViewModelKey(SplashViewModel::class)
internal abstract fun provideSplashViewModel(splashViewModel: SplashViewModel) : ViewModel
}
So how can I use ViewModelKey properly with Kotlin and what is main cause of this error, any help will be appreciated
Your ViewModelKey be like
#MustBeDocumented
#kotlin.annotation.Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
As mentioned this question problem is related to Kotlin version. Using higher than 1.3.30 version solves the problem.

Creating dependent component in dagger 2

I have a project with one AppComponent that builds and works. Now I want to add another dependent component to the project.
I add a scope annotation
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class MyTestScope
Then I create some class and a module for it
class A {
fun get() = 1
}
#Module
class TestModule {
#Provides
#MyTestScope
fun provideA(): A {
return A()
}
}
After I add the dependent component like this
#MyTestScope
#Component(dependencies = [AppComponent::class],
modules = [TestModule::class])
interface DependentComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun appComponent(component: AppComponent): Builder
fun build(): DependentComponent
}
fun inject(application: Application)
}
If I try to build it I see the next error
error: #Component.Builder is missing setters for required modules or components: [AppComponent]
Here is how my AppComponent looks like
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
ActivityModule::class,
// etc ....
])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun language(language: Language): Builder
#BindsInstance
fun appContext(appContext: Context): Builder
fun build(): AppComponent
}
fun inject(application: Application)
}
Any idea what's wrong?
When you create a builder to your dependent component never mark a setter of your main component with #BindsInstance
#MyTestScope
#Component(dependencies = [AppComponent::class],
modules = [TestModule::class])
interface DependentComponent {
#Component.Builder
interface Builder {
fun appComponent(component: AppComponent): Builder
fun build(): DependentComponent
}
fun inject(application: Application)
}

Dagger2 Circular Dependency Error

I am using dagger2 2.16 version for dependency injection inside mine android project. I examine a lot of examples, and although I do not have a similar approach I get the error of "circular dependency".
Mine source code;
AppComponent.kt
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuilderModule::class]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: App)
}
App.kt
class App : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
AppInjector.init(this)
initOneSignal()
}
private fun initOneSignal() = OneSignal.startInit(this).setNotificationOpenedHandler(CustomNotificationOpenedHandler()).inFocusDisplaying(OneSignal.OSInFocusDisplayOption.Notification).init()
override fun activityInjector() = dispatchingAndroidInjector
}
ActivityBuilderModule.kt
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector
abstract fun contributeSplashActivity(): SplashActivity
}
AppModule.kt
#Module(includes = [(ViewModelModule::class)])
class AppModule {
#Singleton
#Provides
fun provideContext(app: Application): Context = app.applicationContext;
#Singleton
#Provides
fun provideApiService(client: OkHttpClient): ApiService {
return Retrofit.Builder()
.baseUrl(Constants.baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(ApiService::class.java)
}
#Singleton
#Provides
fun provideOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder().addInterceptor(interceptor).build()
}
#Singleton
#Provides
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
return interceptor
}
}
If I remove the ActivityBuilderModule from the AppComponent, the project is compiled without problems. But if you add to the modules section, the project gives the error below.
error: [ComponentProcessor:MiscError]
dagger.internal.codegen.ComponentProcessor was unable to process this
interface because not all of its dependencies could be resolved. Check
for compilation errors or a circular dependency with generated code.
Please help me.
In App.kt you need to initialize component with the application context. the line
AppInjector.init(this)
should be inside the Activity i.e. splashActivity in which you're going to inject the dependencies.
The above mentioned error message might also appear, because kotlin stdlib is not declared as dependency. So adding e.g. implementation("org.jetbrains.kotlin:kotlin-stdlib:1.3.60") in your build.gradle(.kts) file might also help.