Android Dagger 2.10 to 2.14.1 - Inject Custom Class - android

I am using Dagger 2.14.1 in my Android application and migrated recently from 2.10. The new version makes it much easier to inject Activities and Fragments, but I can't find a way to inject a custom class where I cannot change the constructor as well. With 2.10 I could write custom inject functions and then use them to simply inject any class:
Simplified Dagger 2.10 Injection with Kotlin:
#Singleton
#Component(dependencies = [], modules = [ApplicationModule::class, ...])
interface ApplicationComponent {
fun application(): App
fun inject(authenticationActivity: AuthenticationActivity)
fun inject(converters: Converters)
// ...
}
class App : Application() {
companion object {
#JvmStatic
lateinit var component: ApplicationComponent
}
override fun onCreate() {
super.onCreate()
component = DaggerApplicationComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
}
// This class is used by the Room database framwork and I cannot change the constructor and do class Converters #Inject constructor(private val gson: Gson) {
class Converters {
#Inject
protected lateinit var gson: Gson
init {
App.component.inject(this)
}
// ...
}
Simplified Dagger 2.14.1 Injection with Kotlin doesn't provide me with a ApplicationComponent Object to inject my custom classess:
#Singleton
#Component(modules = [ApplicationModule::class, ...])
interface ApplicationComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
fun inject(converters: Converters) // doesn't work!
}
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
// From where do I get my component to call .inject() from another class?
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
In short: the DaggerApplicationComponent.builder().create(this) returns an AndroidInjector<App!> Object. This Object only has one .inject() functions which only accepts my App Class. So I cannot inject anything else.
Of course there is a lot missing, but I wanted to only post the relevant code. My Dagger 2.14.1 implementation works and all my dependencies in Activities, Fragments and View Models get injected.
I am quite new to Dagger and the more I use it the more I love it, but I couldn't figure out on how to inject custom classes where I cannot change the constructor. Thanks for you help!

Try this:
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
companion object {
#JvmStatic
lateinit var instance: App
}
#Inject
lateinit var component: ApplicationComponent
override fun onCreate() {
super.onCreate()
instance = this
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
Then
object Injector {
#JvmStatic fun get(): ApplicationComponent = App.instance.component
}
Now you can do
class Converters {
#Inject
protected lateinit var gson: Gson
init {
Injector.get().inject(this)
}

Related

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?

How to have view models implement test services, while dagger 2 setup extends DaggerApplication, and uses a Component.Builder

During testing, my view models are still using production services, and I want them to use test services. I'm using robolectric for testing, and still haven't found a solution to my problem.
What I thought would be most promising would be using a viewModelFactory generator in my test fragment, like ViewModelUtil.createFor(mockedViewModel) is used in the GitHubBrowserSample. But that's not working for me, my test services created in the mocked viewModel will then get replaced by production services when running startFragment as shown below.
My main application extends DaggerApplication() and my production component uses a #Component.Builder so I'm not able to specify a module in my building process.
Additionally, I've tried having TestAppComponent extend from my AppComponent but that doesn't seem to do the trick either.
I've also tried setting a builder as a static object in MyApplication.kt like so:
companion object {
var builder: AndroidInjector.Factory<MyApplication> =
DaggerAppComponent.builder()
}
...
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return builder.create(this)
}
And in my FragmentTest.kt init doing something like:
private fun init() {
bagFragment = BagFragment.newInstance()
MyApplication.builder = DaggerTestAppComponent.builder()
SupportFragmentTestUtil.startFragment(bagFragment, AppCompatActivity::class.java)
}
But that doesn't work because of a type mismatches. If there was some solution where extending from DaggerApplication() was still a possibility I would be very interested in that.
Here's my setup:
AppComponent.kt
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityModule::class,
FragmentModule::class,
ProductionModule::class])
interface AppComponent : AndroidInjector<MyApplication>{
#Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApplication>()
}
ProductionModule.kt
#Module
open class ProductionModule {
#Provides
fun browseCategoriesViewModel(authenticationService: AuthenticationService): BrowseCategoriesFragment.BrowseCategoriesViewModel{
return BrowseCategoriesFragment.BrowseCategoriesViewModel(authenticationService)
}
// Provides multiple things
...
ViewModelModule.kt
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(factor: ViewModelFactory): ViewModelProvider.Factory
/*** Fragments ***/
#Binds
#IntoMap
#ViewModelKey(BagFragment.BagViewModel::class)
abstract fun bindBagViewModel(viewModel: BagFragment.BagViewModel): ViewModel
MyApplication.kt
class MyApplication: DaggerApplication(), HasActivityInjector, HasSupportFragmentInjector {
#Inject
lateinit var sharedPreferencesService: SharedPreferencesService
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
#Inject
lateinit var supportFragmentInjector: DispatchingAndroidInjector<Fragment>
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}
override fun activityInjector() = activityInjector
override fun supportFragmentInjector() = supportFragmentInjector
}
The AppModule, FragmentModule, and ActivityModule are set up according to documentation, and haven't given me any issues.
Here is my test setup:
TestAppComponent.kt
#Singleton
#Component(modules =[
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
TestModule::class])
interface TestAppComponent: AndroidInjector<MyApplication>{
#Component.Builder
abstract class Builder: AndroidInjector.Builder<MyApplication>()
}
TestModule.kt
#Module
internal class TestModule {
#Provides
fun bagViewModel(dataService: FakeDataService,
authenticationService: FakeAuthenticationService,
favoritesService: FakeFavoritesService,
cartService: FakeCartService): BagFragment.BagViewModel {
return BagFragment.BagViewModel(dataService, authenticationService, favoritesService, cartService)
}
#Provides
#Singleton
fun dataService(): FakeDataService = FakeDataService()
#Provides
#Singleton
fun authenticationService(): FakeAuthenticationService = FakeAuthenticationService()
// provides many other things
My current test fragment init method looks like this:
private fun init() {
bagFragment = BagFragment.newInstance()
SupportFragmentTestUtil.startFragment(bagFragment, AppCompatActivity::class.java)
}
Last but not least, an example production fragment:
BagFragment.kt
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var viewModel: BagViewModel
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this, viewModelFactory).get(BagViewModel::class.java)
}
When I call SupportFragmentTestUtil.startFragment() i'm assuming that AndroidInjection.inject(this) will replace my fake test module with the production module. I've also tried putting AndroidInjection.inject(this) in an init method as opposed to onCreate but that gives me problems else where and is contrary to documentation. Please help!

Dagger-2 "lateinit property application has not been initialized"

I am trying to inject application context in a class which is giving
”lateinit property application has not been initialized"
exception.
CoreModule.kt
#Module
open class CoreModule {
#Singleton
#Provides
fun provideRealmHelper(): RealmHelper {
return RealmHelper()
}
}
MyApplication.kt
open class MyApplication : MultiDexApplication(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
val log = LoggerFactory.getLogger(this.javaClass)!!
companion object {
var application: MyApplication? = null
fun getInstance(): MyApplication {
return application!!
}
}
override fun onCreate() {
try {
super.onCreate()
application = this
DaggerAppComponent.builder().application(this).build().inject(this)
} catch (e: Exception) {
log.error("Exception in Application", e)
Thread.setDefaultUncaughtExceptionHandler(GlobalExceptionHandler())
}
}
override fun activityInjector() = dispatchingAndroidInjector
}
AppComponent.kt
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class,CoreModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: MyApplication): Builder
fun build(): AppComponent
}
fun inject(myApplication: MyApplication)
fun inject(realmHelper: RealmHelper)
}
//I need application context in this class. I am inject applicationContext here.
Is injecting is correct way to do or I should use constructor injection?
RealmHelper.kt
class RealmHelper #Inject constructor() {
//need application context here but getting "lateinit property application has not been initialized
#Inject
lateinit var application: MyApplication
init {
Realm.init(application) // null application
}
}
Note: MyApplication is added to AndoridManifest.xml
The problem is you annotated your field but not injected. You can inject field like you did in application class : DaggerAppComponent.builder().application(this).build().inject(this)
or you can move your application field to RealmHelper constructor and in core module you need to write a provide function to return application. If you want to see an example I have an applicaton. https://github.com/volkansahin45/Moneycim
I need application context in this class. I am inject
applicationContext here. Is injecting is correct way to do or I should
use constructor injection?
Always favor constructor injection over field injection if possible.
Your CoreModule is not needed. The code below is enough.
#Singleton
class RealmHelper #Inject constructor(private val application: MyApplication) {
//Your implementation
}
fun inject(realmHelper: RealmHelper) in your Component is also unnecessary.
Removing those lines should fix it, I quickly threw together a demo project just to test it to make sure. Here is a quick gist with the code.
this might be too late but this may help other developer..
#set:Inject
internal var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>? = null
use this instead of
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
and in case you get same error elsewhere, do the same. Use #set:Inject instead of #Inject and use internal instead of lateinit.
This worked for me like charm.

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 can i inject object into presenter in android kotlin MVP mosby app with dagger

I am trying to get dagger working in my application.
After creating Module Component and MyApp i can use dagger to inject database service into view but i am having trouble doing same thing with presenter.
Code:
class MyApp : Application() {
var daoComponent: DaoComponent? = null
private set
override fun onCreate() {
super.onCreate()
daoComponent = DaggerDaoComponent.builder()
.appModule(AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.daoModule(DaoModule())
.build()
}
}
Module
#Module
class DaoModule {
#Provides
fun providesEstateService(): EstateService = EstateServiceImpl()
}
Component
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(activity: MainActivity)
}
AppModule
#Module
class AppModule(internal var mApplication: Application) {
#Provides
#Singleton
internal fun providesApplication(): Application {
return mApplication
}
}
MainActivity
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
#Inject
lateinit var estateService : EstateService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MyApp).daoComponent!!.inject(this)estateService.numberOfInvoicedEstates.toString()
}
override fun createPresenter(): MainPresenter = MainPresenterImpl()
}
After injecting estateService this way I can use it, but I cant figure out how do I inject service directly into the presenter.
I tried doing it like this but it isn't working.
Should I just pass injected objects from the activity? or maybe I should pass MyApp as an argument or make static method allowing my to get it from any place in the application?
class MainPresenterImpl
#Inject
constructor(): MvpBasePresenter<MainView>(),MainPresenter {
#Inject
lateinit var estateService : EstateService
}
Your component should provide the presenter like that:
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
#Singleton
interface MyComponent {
mainPresenter() : MainPresenter
}
And all dependencies the presenter needs are injected to the presenter via constructor parameters:
class MainPresenterImpl
#Inject constructor(private val estateService : EstateService ) :
MvpBasePresenter<MainView>(),MainPresenter {
...
}
Than in createPresenter() just grab the presenter from dagger component like this:
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
...
override fun createPresenter(): MainPresenter =
(application as MyApp).myComponent().mainPresenter()
}
Please note that the code shown above will not compile. This is just pseudocode to give you an idea how the dependency graph could look like in Dagger.
Please refer to this example on how to use Dagger 2 in combination with MVP.
You have to tell your component where you want to inject it.
So, try with this component:
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(presenter: MainPresenterImpl)
}

Categories

Resources