Inject dependency that requires parameter in constructor - android

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()
}

Related

android - Dagger 2 injecting multiple interface and the implementation class

So i want to build multi modular android project and have a problem with injecting the interface and the implementation.
I have two interface for navigation between fragment
first interface
interface Fragment1Navigation {
fun navigateToFragment1()
fun navigateToFragment2()
}
second interface
interface Fragment2Navigation {
fun navigateToFragment3()
fun navigateToFragment4()
}
then i have the class that implement those two interfaces
class Navigator: BaseNavigator(), Fragment1Navigation, Fragment2Navigation {
override fun navigateToFragment1() {
// some implementation
}
override fun navigateToFragment2() {
// some implementation
}
override fun navigateToFragment3() {
// some implementation
}
override fun navigateToFragment4() {
// some implementation
}
}
i want to inject this Navigator class in my mainActivity to bind my navcontroller first and also i want to inject the interface in other fragment and i made the module class like this
#Module()
class NavigationModule {
#Provides
#Singleton
fun provideNavigator(): Navigator = Navigator()
#Provides
fun provideFragment1Navigation(): Fragment1Navigation = Navigator()
#Provides
fun provideFragment2Navigation(): Fragment2Navigation = Navigator()
}
I want those provideFragmentNavigation and provideNavigatioin to provide the same Navigator() so interface in fragment lead to same navigator like in main activity, but turns out it provide different instance so the interface in fragment lead to different navigator from mainactivity
Two ways u can achieve this:
using #Binds instead of #Provides
passing the Navigation() object directly to the constructor of NavigationModule
using binds
class Navigator #Inject constructor(): BaseNavigator(), Fragment1Navigation, Fragment2Navigation {
// body
}
Module
#Module()
abstract class NavigationModule {
#Binds
abstract fun provideNavigator(navigator: Navigator): BaseNavigator
#Binds
abstract fun provideFragment1Navigation(navigator: Navigator): Fragment1Navigation
#Binds
abstract fun provideFragment2Navigation(navigator: Navigator): Fragment2Navigation
}

Not able to inject a class using SubComponent in Dagger 2

I'm trying to add Dagger 2 in one project. I have created 3 components
AppComponent (the primary one)
RetrofitComponent (Dependent component works fine)
ControllerComponent (Subcomponent, not injecting properly)
this is my AppComponent
#ApplicationScope
#Component(modules = [ApplicationModule::class, PrefModule::class])
interface AppComponent {
fun inject(instance: AppInstance)
fun getAppPref(): AppPreference
fun newControllerComponent(controllerModule: ControllerModule): ControllerComponent
}
this is my ControllerComponent
#UIScope
#Subcomponent(modules = [ControllerModule::class])
interface ControllerComponent {
fun injectActivity(activity: BaseActivity)
fun injectDialog(dialog: BaseDialog)
}
ControllerModule
#Module
class ControllerModule(activity: FragmentActivity) {
var mActivity: FragmentActivity
init {
mActivity = activity
}
#Provides
fun getContext(): Context {
return mActivity
}
#Provides
fun getActivity(): Activity {
return mActivity
}
#Provides
fun getFragmentManager(): FragmentManager {
return mActivity.supportFragmentManager
}
#Provides
fun getDialogManager(fragmentManager: FragmentManager): DialogsManager
{
return DialogsManager(fragmentManager)
}
#Provides
fun getDialogsFactory(): DialogsFactory {
return DialogsFactory()
}
}
Injecting the activity is working fine, but when I try to inject the Dialog its never injecting
controller.injectDialog(singleDialog)
My entire code is here Github link. Need some help. What am I not doing or doing wrong that I can't inject from subcomponent.
To use field injection on a given class, you need to specify the concrete type rather than its superclass.
#UIScope
#Subcomponent(modules = [ControllerModule::class])
interface ControllerComponent {
//fun injectActivity(activity: BaseActivity)
//fun injectDialog(dialog: BaseDialog)
fun inject(activity: SomeSpecificActivity)
fun inject(activity: SomeOtherActivity)
fun inject(dialog: SomeSpecificDialog)
fun inject(dialog: SomeOtherDialog)
}
The Injection is working, but you are injecting nothing inside SingleClickDialog class, remember than the method inject(class : Class) is for let it to know dagger that class will be injected, but you need to put the elements you need inject inside that class with the annotation #Inject, and those elements you need to add its provider in ControllerModule as well

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)
}

How to inject Context into a Presenter using Dagger 2

I've been checking recently Dagger 2.14.1 with the new Android injectors.
I'm using MVP and the Presenter is getting inject into the View correctly:
class CustomApplication : Application(), HasActivityInjector {
#Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
DaggerApplicationComponent
.builder()
.create(this)
.inject(this)
}
override fun activityInjector(): DispatchingAndroidInjector<Activity> {
return activityDispatchingAndroidInjector
}
}
--
#Singleton
#Suppress("UNUSED")
#Component(modules = arrayOf(AndroidInjectionModule::class, ApplicationModule::class, ActivityBuilder::class))
interface ApplicationComponent : AndroidInjector<CustomApplication> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<CustomApplication>()
override fun inject(application: CustomApplication)
}
--
#Module
class ApplicationModule {
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
}
--
#Module
#Suppress("UNUSED")
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = arrayOf(ActivitiesModule::class))
internal abstract fun bindSplashActivity(): SplashActivity
}
--
#Singleton
class SplashPresenter #Inject constructor() {
fun test() {
Log.d("TAG", "this is a test")
}
}
Now, instead of having the logged message harcoded, I would like to get it from string.xml, so I tried this:
#Singleton
class SplashPresenter #Inject constructor(private val context: Context) {
fun test() {
Log.d("TAG", context.getString(R.strings.test))
}
}
But then I get this error:
Error:(7, 1) error: [dagger.android.AndroidInjector.inject(T)]
android.app.Application cannot be provided without an #Inject
constructor or from an #Provides-annotated method.
Could anyone tell me please how to inject the app context (or the resources) into the presenter?
Thanks.
You're using CustomApplication with Dagger in your ApplicationComponent, so that's what it knows about. It doesn't try to resolve types on its own, so Application is some class Dagger never heard about.
You can either add another #Provides / #Binds to bind CustomApplication > Application > Context or just go the direct way and change your code to require a CustomApplication instead of Application:
#Provides
#Singleton
fun provideContext(application: CustomApplication): Context {
return application
}
// ... or alternatively ...
#Provides
#Singleton
fun provideApplication(application: CustomApplication): Application {
return application
}
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
Either way your application can then be used as a Context.

Categories

Resources