Dagger 2 - IncompatiblyScopedBindings - android

I’m trying to create an basic architecture with Dagger 2 for my study project but I have encountered several problems with it…
The current error daggers tell me
FeedMeApplicationComponent.java:7: error: [Dagger/IncompatiblyScopedBindings] .FeedMeApplicationComponent (unscoped) may not reference scoped bindings:
I only have this problem when I add the ActivityMainModule as a module of the application
and ActivityMainModule contains a sub component only related to the MainActivity.
I don’t understand why I cannot add this module of sub component to the Application Graph :confusing
Those are my Dagger classes…
class FeedMeApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerFeedMeApplicationComponent.factory().create(this)
}
}
#Component(modules = [AndroidInjectionModule::class, NetworkModule::class, NutritionModule::class, ActivityMainModule::class])
interface FeedMeApplicationComponent : AndroidInjector<FeedMeApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): FeedMeApplicationComponent
}
override fun inject(instance: FeedMeApplication?)
}
#Module
object NetworkModule {
#Singleton
#Provides
#JvmStatic
fun provideNutritionService(retrofit: Retrofit): NutritionService {
return retrofit.create(NutritionService::class.java)
}
#Singleton
#Provides
#JvmStatic
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl("https://api.edamam.com/api")
.client(okHttpClient)
.build()
}
#Singleton
#Provides
#JvmStatic
fun provideOkHttp(): OkHttpClient {
return OkHttpClient()
.newBuilder()
.addInterceptor(ApiInterceptor())
.build()
}
private class ApiInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
request
.addHeader("api_id", “abc")
.addHeader("app_key", “123")
return chain.proceed(request.build())
}
}
}
#Module
object NutritionModule {
#Singleton
#Provides
#JvmStatic
fun provideNutritionRepository(nutritionService: NutritionService): NutritionRepository {
return NutritionRepository(nutritionService)
}
}
#Module(subcomponents = [MainActivityComponent::class], includes = [MainModule::class])
abstract class ActivityMainModule {
#Binds
#IntoMap
#ClassKey(MainActivity::class)
abstract fun bindAndroidInjector(factory: MainActivityComponent.Factory): AndroidInjector.Factory<*>
}
#Module
object MainModule {
#Singleton
#Provides
#JvmStatic
fun provideMainViewModelFactory(nutritionRepository: NutritionRepository): MainViewModel.Factory {
return MainViewModel.Factory(nutritionRepository)
}
#Provides
#JvmStatic
fun provideMainViewModel(
viewModelFactory: MainViewModel.Factory,
fragmentActivity: FragmentActivity
): MainViewModel {
return ViewModelProviders.of(fragmentActivity, viewModelFactory)
.get(MainViewModel::class.java)
}
}
#ActivityScope
#Subcomponent
interface MainActivityComponent : AndroidInjector<MainActivity> {
#Subcomponent.Factory
interface Factory : AndroidInjector.Factory<MainActivity> {}
}
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel.liveDataFoodAnalysis.observe(this, Observer { food ->
Log.d("Food answer", food.uri)
})
mainViewModel.getFoodAnalysisResponse("egg")
}
}

You will have to annotate FeedMeApplicationComponent with #Singleton. because both NetworkModule and NutritionModule define #Provides functions scoped with #Singleton, any component that uses these modules must also specify its own scope as #Singleton.
From Dagger docs: -
Since Dagger 2 associates scoped instances in the graph with instances
of component implementations, the components themselves need to
declare which scope they intend to represent. For example, it wouldn’t
make any sense to have a #Singleton binding and a #RequestScoped
binding in the same component because those scopes have different
lifecycles and thus must live in components with different lifecycles.
To declare that a component is associated with a given scope, simply
apply the scope annotation to the component interface.

Related

Dagger2 : Cannot be provided without an #Inject constructor or an #Provides-annotated method

I've just refactored my dagger code to make it scalable and move all core stuff to a separate module called di.
Now when I try to inject my dependencies in the app module I got this :
[Dagger/MissingBinding] retrofit2.Retrofit cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface ResetPasswordComponent {
^
retrofit2.Retrofit is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordNetworkModule.providerResetPasswordAPI(retrofit)
com.sahra.oms.ibshop.data.remote.service.ResetPasswordService is injected at
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl(resetPasswordService)
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindUniqueIdRepository(uniqueRepositoryImpl)
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueIdRepository is injected at
com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel(repository)
com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindCheckIdViewModel(checkUniqueIDViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.sahra.oms.ibishop.di.util.ViewModelFactory(viewModelsMap)
com.sahra.oms.ibishop.di.util.ViewModelFactory is injected at
com.sahra.oms.ibshop.di.ViewModelBuilder.bindViewModelFactory(arg0)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment.viewModelFactory
com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordComponent.inject(com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment)
ResetPasswordService is just a Retrofit interface.
Here is my code:
AppComponent
#Singleton
#AppScope
#Component
interface AppComponent {
fun provideContextComponent(): ContextComponent
fun provideNetworkComponent(): NetworkComponent
fun provideSharedPrefComponent(): SharedPreferencesComponent
fun inject(app: Application)
#Component.Factory
interface Factory {
fun create(
#BindsInstance
context: ContextComponent,
#BindsInstance
network: NetworkComponent,
#BindsInstance
sharedPrefs: SharedPreferencesComponent
): AppComponent
}
}
NetworkComponent :
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class NetworkScope
#NetworkScope
#Component(
dependencies = [ContextComponent::class],
modules = [OkHttpModule::class, AuthBinderModule::class]
)
interface NetworkComponent {
fun provideOkHttp(): OkHttpClient
fun provideRetrofit(): Retrofit
fun provideGson(): GsonConverterFactory
}
OkHttpModule :
#Module
object OkHttpModule {
private const val BASE_URL = "base_url"
#Provides
#JvmStatic
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message -> Log.d("<<<network>>>", message) }).apply {
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
}
}
#Provides
#JvmStatic
fun provideChuckInterceptor(app: Application): ChuckInterceptor = ChuckInterceptor(app)
#Provides
#JvmStatic
fun provideOkhttpCache(app: Application): Cache =
Cache(app.cacheDir, 50_000_000)
#Provides
#NetworkScope
#JvmStatic
fun provideClient(
loggingInterceptor: HttpLoggingInterceptor,
chuckInterceptor: ChuckInterceptor,
authInterceptor: Interceptor,
cache: Cache
): OkHttpClient {
return OkHttpClient.Builder()
.cache(cache)
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.addInterceptor(chuckInterceptor)
.build()
}
#Provides
#NetworkScope
#JvmStatic
fun provideGson() = Gson()
#Provides
#JvmStatic
fun provideGsonConverter(gson: Gson) = GsonConverterFactory.create(gson)
#Provides
#NetworkScope
#JvmStatic
fun provideRetrofit(
gsonConverterFactory: GsonConverterFactory,
client: Lazy<OkHttpClient>
): Retrofit = Retrofit.Builder()
.callFactory { request -> client.get().newCall(request) }
.baseUrl(BASE_URL)
.addConverterFactory(gsonConverterFactory)
.build()
}
Here is how I try to inject the dependencies:
#FeatureScope
#Component(
dependencies = [AppComponent::class],
modules = [
ResetPasswordNetworkModule::class,
ResetPasswordModule::class,
ViewModelBuilder::class
]
)
interface ResetPasswordComponent {
fun inject(newPasswordFragment: NewPasswordFragment)
fun inject(checkUniqueIDFragment: CheckUniqueIDFragment)
#Component.Builder
interface Builder {
fun coreComponent(appComponent: AppComponent): Builder
fun build(): ResetPasswordComponent
}
}
#Module
abstract class ResetPasswordModule {
#Binds
abstract fun bindResetPasswordRepository(resetPasswordRepositoryImpl: ResetPasswordRepositoryImpl): ResetPasswordRepository
#Binds
abstract fun bindUniqueIdRepository(uniqueRepositoryImpl: UniqueRepositoryImpl): UniqueIdRepository
#Binds
#IntoMap
#ViewModelKey(CheckUniqueIDViewModel::class)
abstract fun bindCheckIdViewModel(checkUniqueIDViewModel: CheckUniqueIDViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(NewPasswordViewModel::class)
abstract fun bindNewPasswordViewModel(newPasswordViewModel: NewPasswordViewModel): ViewModel
}
#Module
object ResetPasswordNetworkModule {
#Provides
#JvmStatic
#FeatureScope
fun provideUserAPI(
retrofit: Retrofit
): ResetPasswordService = retrofit.create(ResetPasswordService::class.java)
}
and here is my repository code:
class ResetPasswordRepositoryImpl #Inject constructor(
private val resetPasswordService: ResetPasswordService
) : ResetPasswordRepository {
}
Fragment :
class NewPasswordFragment{
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
}
class NewPasswordViewModel #Inject constructor(
private val repository: ResetPasswordRepository
)
Thanks in advance.
Your app component factory looks like this:
fun create(
#BindsInstance
context: ContextComponent,
#BindsInstance
network: NetworkComponent,
#BindsInstance
sharedPrefs: SharedPreferencesComponent
): AppComponent
This provides access to an instance of NetworkComponent, so any #Provides, #Binds, or #Inject that requires a NetworkComponent can get one. It does not, however, give direct access to NetworkComponent's object graph.
NetworkComponent already exposes Retrofit, so you can certainly get one if you have access to the component. However, this process is not automatic, and in your setting requires a #Provides method.
#Provides
fun provideRetrofit(component: NetworkComponent): Retrofit = component.provideRetrofit()
This is more convoluted that it needs to be. A better way to accomplish this is to make NetworkComponent a dependency of AppComponent (or just use its modules and drop the network component entirely), then expose Retrofit in AppComponent.
// using multiple scoped dependencies requires Dagger 2.27
#AppScope
#Component(dependencies = [ContextComponent::class, NetworkComponent::class, SharedPreferencesComponent::class])
interface AppComponent {
fun provideRetrofit(): Retrofit
// ...
}

Inject dependency that requires parameter in constructor

I'm creating an application in Kotlin using the MVP pattern.
I would need to inject a Repository into my Presenter for this purpose. Except that for this, my Repository requires a Retrofit interface as a parameter of its constructuor.
I'm a beginner in the use of Dagger2, and the answers found on the internet are far too complicated for such a basic case like mine.
Here's the repository i want to be injected :
class RepositoryInventory(private val api: Service): IRepositoryInventory {
override fun getInventoryItemByNum(itemnum: String): Observable<Response<Item>> {
return api.getInventoryItemByNum(itemnum)
.toObservable()
}
override fun getAllInventoryItems(): Single<Response<Item>> {
return api.getAllInventoryItems()
}
}
My Component
#Singleton
#Component(modules = arrayOf(ActivityModule::class))
interface ActivityComponent {
fun inject(loginActivity: LoginActivity)
fun inject(itemDetailActivity: ItemDetailActivity)
}
My module :
#Module
class ActivityModule(private var activity: Activity) {
#Provides
fun provideActivity(): Activity {
return activity
}
#Provides
fun provideLoginPresenter(): LoginPresenter {
return LoginPresenter()
}
#Provides
fun provideItemDetailPresenter(): ItemDetailPresenter {
return ItemDetailPresenter()
}
}
In my activity, my module is injected with this method :
private fun injectDependency() {
val activityComponent = DaggerActivityComponent.builder()
.activityModule(ActivityModule(this))
.build()
activityComponent.inject(this)
}
I have 2 components and 2 modules: one designed to inject into a fragment and the other into an activity.
Except in my case, I want to inject into a Presenter that is not a Fragment or an Activity but a class
Ok, my guess is you want to inject RepositoryInventory into LoginPresenter. If so, you can make use of #ContributesAndroidInjector and Binds
First, create a LoginActivityModule
#Module
abstract class LoginActivityModule {
#Binds
abstract fun loginPresenter(loginPresenter: LoginPresenter): LoginPresenter
}
Then, create a module called ActivityBindingModule
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = [LoginActivityModule::class])
abstract fun loginActivity(): LoginActivity
#ContributesAndroidInjector()
abstract fun itemDetailActivity(): ItemDetailActivity
}
And change your ActivityComponent like this
#Singleton
#Component(modules = arrayOf(ActivityModule::class, ActivityBindingModule::class))
interface ActivityComponent {
}
And in your LoginPresenter:
class LoginPresenter #Inject constructor(private val repositoryInventory: IRepositoryInventory) {
...
}
Remember to remove this in ActivityModule:
#Provides
fun provideLoginPresenter(): LoginPresenter {
return LoginPresenter()
}

Android UI testing for MVVM architecture with Dagger2, Espresso and Mockito

I'm trying to add UI tests for the first time to an app, and I was looking at using espresso.
The app uses Dagger2 for DI with #Inject annotations for classes that should be injectable, and AndroidInjection/AndroidSupportInjection in screens (Activity / Fragment).
class Application : android.app.Application(), HasActivityInjector, HasServiceInjector {
...
override fun onCreate() {
super.onCreate()
initDagger()
Timber.d("Application initialized successfully!")
}
protected open fun initDagger() {
Components.initialize(this)
}
}
object Components : ComponentFactory {
private lateinit var sComponent: AppComponent
fun initialize(app: Application) {
sAppComponent = DaggerAppComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
// overrides
}
interface ComponentFactory {
fun app(): AppComponent
fun authenticated(): AuthenticatedComponent
}
Next the Components and Modules. The AppActivitiesModule and AuthenticatedActivitiesModule are classes with #ContributesAndroidInjector for screens.
#Singleton
#Component(modules = [AppModule::class, AppActivitiesModule::class, AndroidInjectionModule::class, AndroidSupportInjectionModule::class])
interface AppComponent {
fun authenticatedComponentBuilder(): AuthenticatedComponent.Builder
fun inject(app: Application)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun applicationModule(applicationModule: ApplicationModule): Builder
}
}
#Module
open class AppModule(private val application: Application) {
// some #Provides
}
#AuthenticatedScope
#Subcomponent(modules = [AuthenticatedModule::class, AuthenticatedActivitiesModule::class])
interface AuthenticatedComponent {
fun inject(application: Application)
#Subcomponent.Builder
interface Builder {
fun userModule(module: UserModule): Builder
fun build(): AuthenticatedComponent
}
}
#Module
class AuthenticatedModule(private val userId: Long,
private val userRole: User.Role) {
// Some #Provides #AuthenticatedScope
}
And a typical use case would be:
#Singleton
class AppLevelService
#Inject constructor(...) { ... }
#AuthenticatedScope
class AuthenticatedLevelServices
#Inject constructor(...) { ... }
class ViewModel
#Inject constructor(private val appService: AppLevelService,
private val authService: AuthenticatedLevelServices) { ... }
class MyActivity : BaseActivity {
#Inject
lateinit var vmProvider: Provide<ViewModel>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
}
}
How can I make the test setup for this "type" of dagger usage?
I find a lot of examples of testing with dagger2 but for #Provides annotations, and I'm thinking there should be a way to mock injectable classes with #Inject.
I've tried DaggerMock but I get:
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
Not that it should affect anything, but I'm also using a custom Runner with DexOpener.
Any ideas or good documentation / examples for testing this setup?

Injecting dependencies in a Android ViewModel?

Hi i have a ViewModel that has two var's that get injected using Dagger 2.11.
However it never gets injected in my viewModel whilst everywhere else in my app, these same dependecy Vars get initialised and injected perfectly
Below is my viewModel
class MyViewModel : AndroidViewModel, MyViewModelContract {
private lateinit var pointsBalance: MutableLiveData<PointsBalance>
#Inject
lateinit var accountDelegator: AccountDelegatorContract
#Inject
constructor(application: Application) : super(application)
init {
DaggerApplicationComponent.builder().application(getApplication() as MyApplication).build().inject(this)
}
override fun getPointsBalance(): LiveData<PointsBalance> {
if (!this::pointsBalance.isInitialized) {
//get balance from network api etc
}
return pointsBalance
}
accountDelegator complains that it is not initialised
Below is how i bind this viewModel inside MyModule.class
#Provides
#JvmStatic
#Singleton
fun providesViewModel(viewModel: MyViewModel): MyViewModelContract = viewModel
my custom application
class MyApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
val applicationComponent = DaggerApplicationComponent.builder()
.application(this)
.build()
applicationComponent.inject(this)
return applicationComponent
}
}
my applicationComponent
#Singleton
#Component(modules = arrayOf(MyModule::class,
))
interface ApplicationComponent : AndroidInjector<DaggerApplication> {
fun inject(mApplication: MyApplication)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: MyApplication): Builder
fun build(): ApplicationComponent
}
}
How i use this viewmodel in a activity/fragment
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
I believe issue is that you're not calling inject. You'd need for a start to add following to ApplicationComponent
void inject(MyViewModel viewModel);
What I'm doing here then fwiw is inheriting from AndroidViewModel (which gives access to application instance) then calling following in ViewModel class (you'd need to substitute DaggerProvider.getComponent with however you're accessing component in your code)
init {
DaggerProvider.getComponent(application).inject(this)
}

Cannot be Provided Without an #Provides-annotated Method

There have been many other similar questions, but none of the answers have been applicable to my code. I cannot figure out what I have done wrong.
First I have a NetworkModule that is used as a module for the ApplicationComponent:
#Module
open class NetworkModule {
companion object {
private val BASE = "http://www.example.com/"
}
#Provides #ApplicationScope
fun provideClient(): OkHttpClient = OkHttpClient()
#Provides #ApplicationScope
fun provideMoshi(): Moshi {
return Moshi.Builder().add(InstantAdapter).add(UriAdapter).build()
}
#Provides #ApplicationScope
fun provideRetrofit(client: OkHttpClient, moshi: Moshi): Retrofit {
return Retrofit.Builder().client(client).baseUrl(BASE)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
#Provides #ApplicationScope
fun provideArticleService(retrofit: Retrofit): ArticleService {
return retrofit.create(ArticleService::class.java)
}
}
#ApplicationScope #Component(modules = arrayOf(ContextModule::class, RealmModule::class, NetworkModule::class))
interface ApplicationComponent {}
Then the ApplicationComponent is built in my Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
plantLog()
drawDagger()
}
private fun drawDagger() {
Injector.initializeApplicationComponent(this)
}
// ...
}
object Injector {
lateinit var applicationComponent: ApplicationComponent
private set
fun initializeApplicationComponent(context: Context) {
applicationComponent = DaggerApplicationComponent.builder()
.contextModule(ContextModule(context))
.networkModule(NetworkModule())
.realmModule(RealmModule())
.build()
}
// ...
}
Then I have an ActivityModule that is used in the ActivityComponent (which has ApplicationComponent as a dependency):
#Module
open class ActivityModule(private val activity: AppCompatActivity) {
#Provides #ActivityScope #ActivityContext
fun provideContext(): Context = activity
#Provides #ActivityScope
fun provideFragmentManager(): FragmentManager = activity.supportFragmentManager
}
#ActivityScope #Component(dependencies = arrayOf(ApplicationComponent::class), modules = arrayOf(ActivityModule::class))
interface ActivityComponent {
fun inject(activity: MainActivity)
}
Finally, I create a new ActivityComponent in the MainActivity and #Inject the ArticleService:
class MainActivity : AppCompatActivity() {
#Inject lateinit var service: ArticleService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerActivityComponent.builder()
.applicationComponent(Injector.applicationComponent)
.activityModule(ActivityModule(this))
.build().inject(this)
service.getNewsArticles()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(
{ response -> onNext(response) },
{ error -> onError(error) })
}
// ...
}
But when I try to build I get the following error, even though I believe the provideArticleService() function in NetworkModule is annotated correctly:
ArticleService cannot be provided without an #Provides- or
#Produces-annotated method.
You're missing the provision methods to inherit to your Activity scoped component. Either use subcomponents instead of component dependency, or define the provision methods in your application component.
#ApplicationScope #Component(modules = arrayOf(ContextModule::class, RealmModule::class, NetworkModule::class))
interface ApplicationComponent {
ArticleService articleService();
}

Categories

Resources