dagger2, how can i call #BindsInstance denoted method of SubComponent..? - android

please help me! I have a trouble in use of dagger 2.
I want to bind some dependency in runtime not in compile time inside MainActivity by using #Subcomponent.Builder and #BindsInstance
I have an ApplicationComponent and it has a Builder and its #BindsInstance looks working fine. I can use like below
DaggerApplicationComponent
.builder()
.application(this)
.build()
.inject(this)
but some trouble came from MainActivity...
below are snippets of codes
[ApplicationComponent]
#Singleton
#Component(modules = [ApplicationModule::class])
internal interface ApplicationComponent : AndroidInjector<MyApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
}
[ApplicationModule]
#Module(
includes = [
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
ActivityInjectionModule::class
],
subcomponents = [
MainComponent::class
]
)
internal abstract class ApplicationModule {
#PerActivity
#ContributesAndroidInjector(modules = [SplashModule::class])
abstract fun splashActivity(): SplashActivity
#Binds
#IntoMap
#ActivityKey(MainActivity::class)
abstract fun mainActivity(builder: MainComponent.Builder): AndroidInjector.Factory<out Activity>
}
[MainComponent]
#PerActivity
#Subcomponent(modules = [MainModule::class])
internal interface MainComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>() {
#BindsInstance
abstract fun testClass(mainTestClass: MainTestClass): Builder
}
}
[MainActivity]
internal class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// This works find without runtime injection
// AndroidInjection.inject(this)
/**
*I want to bind some dependency(in this case, MainTestClass) in runtime like below.
* so that I can use MainTestClass inside MainModule to inject this to other classes.
* but, for some reason,
* DaggerMainComponent IS NOT GENERATED AUTOMATICALLY...
*/
DaggerMainComponent.builder()
.testClass(MainTestClass())
.build()
.inject(this);
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startActivity(Intent(this, SplashActivity::class.java))
}
}
The problem is that I cannot access DaggerMainComponent because the Dagger doesn't generate it automatically.
I am searching for lots of websites to solve this but failed.
Is there any way to make it?

I have found a way to achieve what you want, I think. Apologies for not using your particular example, but it was easier to paste in the code I have and know that works from my own IDE. I've added comments on the critical lines. Here's the code:
Singleton component
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
RuntimeBindingModule::class // my addition!
])
interface MainApplicationComponent {
fun inject(app: MainApplication)
// my addition!
fun runtimeBuilder(): RuntimeBindingActivitySubcomponent.Builder
#Component.Builder
interface Builder {
fun build(): MainApplicationComponent
#BindsInstance fun app(app: Context): Builder
}
}
This binding code is essentially identical to yours.
#Subcomponent
interface RuntimeBindingSubcomponent : AndroidInjector<RuntimeBindingActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<RuntimeBindingActivity>() {
#BindsInstance abstract fun bindInt(intVal: Int): Builder
}
}
#Module(subcomponents = [RuntimeBindingSubcomponent::class])
abstract class RuntimeBindingActivityModule {
#Binds #IntoMap #ActivityKey(RuntimeBindingActivity::class)
abstract fun bindInjectorFactory(
builder: RuntimeBindingActivitySubcomponent.Builder
): AndroidInjector.Factory<out Activity>
}
MainApplication
open class MainApplication : Application(), HasActivityInjector {
// This needs to be accessible to your Activities
lateinit var component: MainApplication.MainApplicationComponent
override fun onCreate() {
super.onCreate()
initDagger()
}
private fun initDagger() {
component = DaggerMainApplicationComponent.builder()
.app(this)
.build()
component.inject(this)
}
}
RuntimeBindingActivity
class RuntimeBindingActivity : AppCompatActivity() {
// I had to use #set:Inject because this is a primitive and we can't use lateinit
// on primitives. But for your case,
// `#Inject lateinit var mainTestClass: MainTestClass` would be fine
#set:Inject var intVal: Int = -1
override fun onCreate(savedInstanceState: Bundle?) {
// And this is how you can get runtime binding
val subComponent = (application as MainApplication).component.runtimeBuilder()
with(subComponent) {
seedInstance(this#RuntimeBindingActivity)
bindInt(10) // runtime binding
build()
}.inject(this)
Log.d("RuntimeBindingActivity", "intVal = $intVal")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_runtime_binding)
}
}
It is critically important to note that the subcomponent which you generate in this way doesn't get magically stored somewhere by dagger. If you want your late-bound instance to be available for injecting into other classes controlled by your #PerActivity scope, you need to manually manage the lifecycle of this subcomponent. Store it somewhere (maybe in your custom Application class), and then you also must set its reference to null when your activity is destroyed, or you'll be leaking that activity.

Related

Cannot inject object provided in AppModule to Activity in a dynamic feature module with Dagger2

With the set up below i'm not be able to inject a Singleton object to a Activity inside a dynamic feature module. I can inject to a subComponent, MainActivity, but not to an Activity that's in dynamic feature module.
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
My AppModule
#Module(includes = [AppProviderModule::class])
abstract class AppModule {
#Binds
abstract fun bindContext(application: Application): Context
}
#Module
object AppProviderModule {
#Provides
#Singleton
fun provideSharedPreferences(application: Application): SharedPreferences {
return application.getSharedPreferences("PrefName", Context.MODE_PRIVATE)
}
}
Dynamic Feature Module GalleryComponent
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
}
And MyApplication
open class MyApplication : Application() {
// Instance of the AppComponent that will be used by all the Activities in the project
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
// Creates an instance of AppComponent using its Factory constructor
// We pass the applicationContext that will be used as Application
return DaggerAppComponent.factory().create(this).apply {
inject(this#MyApplication)
}
}
}
Activity in dynamic feature module, when only inject GalleryViewer and DummyDependency is injected from GalleryModule it works fine
class GalleryActivity : AppCompatActivity() {
#Inject
lateinit var sharedPreferences: SharedPreferences
#Inject
lateinit var galleryViewer: GalleryViewer
#Inject
lateinit var dummyDependency: DummyDependency
override fun onCreate(savedInstanceState: Bundle?) {
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.build()
.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
}
When i try to inject SharedPreferences or any dependency that does not depend on any arguments like context or application from AppModule i get error
error: [Dagger/MissingBinding] android.content.SharedPreferences cannot be provided without an #Provides-annotated method.
The error here is not including provision method for dependencies in AppComponent and not building dynamic feature component properly.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
/**
* 🔥🔥🔥 This method is required to get this object from a class that uses this component
* as dependent component
*/
fun provideSharedPreferences(): SharedPreferences
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
In dynamic feature
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
// Alternative1 With Builder
#Component.Builder
interface Builder {
fun build(): GalleryComponent
#BindsInstance
fun application(application: Application): Builder
fun galleryModule(module: GalleryModule): Builder
fun appComponent(appComponent: AppComponent): Builder
}
// Alternative2 With Factory
#Component.Factory
interface Factory {
fun create(appComponent: AppComponent,
galleryModule: GalleryModule,
#BindsInstance application: Application): GalleryComponent
}
}
You must either use Builder or Factory, with hilt none of this might be necessary in the future, however it does not support dynamic feature yet, i rather factory pattern since they deprecated Builder pattern.
inside Activity onCreate initialize injection
private fun initInjections() {
// Alternative1 With Builder
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.application(application)
.galleryModule(GalleryModule())
.build()
.inject(this)
// Alternative2 With Factory
DaggerGalleryComponent
.factory()
.create((application as MyApplication).appComponent, GalleryModule(), application)
.inject(this)
}
You should choose the same pattern used inside feature component.

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?

Android Kotlin with Dagger2, lateinit var has not been initialized

I'm a Dagger newb and have a trouble with using it.
What I want to develop is that using RxAndroidBle and to initialize it by Dagger for providing Context.
So I researched how it can be implemented, and I wrote some codes and It seems to be working for me but not working at all.
The followings are my codes.
AppComponent.kt
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
#Named("appContext")
#Singleton
fun provideContext(application: Application): Context =
application.applicationContext
}
BluetoothModule.kt
#Module
class BluetoothModule {
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient(#Named("appContext") context: Context):RxBleClient =
RxBleClient.create(context)
}
BluetoothController.kt for injecting by DaggerApplication.
class BluetoothController : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this).build()
}
}
I've inserted
android:name".BluetoothController"
to AndroidManifest.xml
And this is how I would use it.
#field:[Inject Named("rxBleClient")]
lateinit var rxBleClient: RxBleClient
But it always occurs an error says: lateinit property context has not been initialized
What things I've missed? Can anyone help me?
Thanks in advance.
Add the below code to make this happen.
Create ActivityBuilderModule for injecting within the activity. Consider our activity as MainActivity
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector(modules=[MainActivityModule::class])
abstract fun contributeSplashActivity(): MainActivity
}
Create your MainActivityModule
#Module
class MainActivityModule{
#Provides()
fun contributeSplashActivity(mainActivity: MainActivity):
MainActivity=mainActivity
}
Modify your component.
#Singleton
#Component(modules = [
AppModule::class,
BluetoothModule::class,
ActivityBuilderModule::class,
AndroidInjectionModule::class])
interface AppComponent : AndroidInjector<BluetoothController> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
Within MainActivity just inject.
class MainActivity{
...
#Inject
lateinit var rxBleClient: RxBleClient
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
}
Let us know in case of any issue.
the context object is not initialized before its called
Though i dont know, how chained your initialization are.
Use #Inject to add deppendencies, do something like this
#Module
class BluetoothModule(val context : Context) {
//#Inject private lateinit var context : Context
#Provides
#Named("rxBleClient")
#Singleton
fun provideRxBleClient():RxBleClient =
RxBleClient.create(context)
}
let your call be like this
val component = AppComponent()
component.bluetoothModule(appContext)
.//other calls here
.build()

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

Dagger 2 not injecting sharedPreference

Hi i am new to dagger 2 and trying to inject an instance of sharedPreference inside my MyActivity class below:
class MyApplication : Application() {
companion object {
#JvmStatic lateinit var applicationComponent : ApplicationComponent
}
override fun onCreate() {
super.onCreate()
applicationComponent = DaggerApplicationComponent.builder().androidModule(AndroidModule(this)).build()
}
}
Here is the component and modules
#Singleton
#Component(modules = arrayOf(AndroidModule::class))
interface ApplicationComponent {
fun inject(mainActivity: MainActivity)
}
#Module
class AndroidModule (private val application: Application){
#Provides
#Singleton
fun provideApplicationContext() : Context = application
#Provides
#Singleton
fun provideSharedPreference() : SharedPreferences = application.getSharedPreferences("shared pref", Context.MODE_PRIVATE)
}
class MainActivity: Activity{
#Inject
internal lateinit var sharedPreference: SharedPreferences
#Inject
internal lateinit var MainScreenPresenter: MainScreenContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_Screen)
MyApplication.applicationComponent.inject(this)
sharedPreference.toString()
initiateViews()
}
}
I get the error below:
Error:(7, 1) error: android.content.SharedPreferences cannot be provided without an #Provides- or #Produces-annotated method.
You have done it a little bit incorrect. First of all now there is dagger-android that helps with the problem of principle that solves the problem that components (such as Activities) should not know about how the injection happens.
you can read it here: https://medium.com/#iammert/new-android-injector-with-dagger-2-part-1-8baa60152abe
Just to be sure that dagger dependencies are in the Android project:
android {
kapt {
generateStubs = true
}
}
compile "com.google.dagger:dagger:2.13"
compile "com.google.dagger:dagger-android:2.13"
compile "com.google.dagger:dagger-android-support:2.13"
kapt "com.google.dagger:dagger-compiler:2.13"
kapt "com.google.dagger:dagger-android-processor:2.13"
Your mistake is that you didn't tell to your graph that you want to make injections into the MainActivity. In the best way you should create Subcomponent for MainActivity, connect it with another Module for MainActivity that have injections that you want to inject into the MainActivity, set in your AppComponent the connection with Subcomponent and only than in MainAcitivy onCreate() method inject your dependency graph. But with dagger-android everything is easier.
Here is the code:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<DaggerApplication> {
fun inject(application: MyApplication)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance fun application(application: MyApplication): Builder
fun build(): AppComponent
}
}
AndroidSupportInjectionModule.class : This goes from the dagger.android.support library. And it provides Android components (Activities/Fragments/Services/BroadcastReceiver/ContentProvider) with our module.
#Component.Builder in dagger2.10 provides us better way to create a builder of DaggerAppComponent.
#BindsInstance in the Builder will automatically create an instance of MyApplication so in AppModule we don't need to instantiate with MyApplication. It is already a dependency in the graph.
ActivityBindingModule.clas is our. I will tell about it later.
You can read more about this part here: https://proandroiddev.com/dagger-2-component-builder-1f2b91237856
Next is AppModule.class :
#Module
abstract class AppModule{
#Binds
abstract fun provideContext(application: MyApplication) : Context
#Module
companion object {
#JvmStatic
#Provides
fun provideSharedPreferences(context: Context): SharedPreferences =
context.getSharedPreferences("SharedPreferences", Context.MODE_PRIVATE)
}
}
#Binds annotation replaces #Provides annotation and it just returns the value in the function parameter. As you see an instance of MyApplication is already in the graph and there is no need to inject MyApplication in the AppModule constructor.
NOTE: function with #Binds annotation should be abstract, and if there are function with #Provides annotation they should be static.
The solution in Kotlin for static funcitons you can find here: https://github.com/google/dagger/issues/900
ActivityBindingModule.class:
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun bindMainActivity(): MainActivity
}
With the ActivityBindingModule class we just create separate Module that will create Subcomponents for Android components for us.
More about ContributesAndroidInjector and Binds you can read here:
https://proandroiddev.com/dagger-2-annotations-binds-contributesandroidinjector-a09e6a57758f
MainActivityModule.class:
#Module
abstract class MainActivityModule {
#Binds
internal abstract fun provideMainActivity(activity: MainActivity): MainActivity
}
MyApplication.class:
class MyApplication: DaggerApplication(){
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
val appComponent = DaggerAppComponent.builder()
.application(this)
.build()
appComponent.inject(this)
return appComponent
}
}
Do not forget insert Application in the Mainfest file.
<application
android:name=".MyApplication"
...
>
...
</application>
For your Application class you need to implement DaggerApplication that implements HasActivityInjector/HasFragmentInjector/etc as well as call AndroidInjection.inject().
About this you can read more here : https://google.github.io/dagger/android.html
MainActivity.class:
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("TAAAAG", sharedPreferences.toString())
}
}
As you can see MainActivity now does not know how SharedPreferences are injected. Actually there is AndroidInjection.inject(this); in the DaggerAppCompatActivity. If you don't extend you class from this, than you need to specify it in onCreate method by yourself, otherwise no injections will be done.
EDIT:
You can check the code from GitHub: https://github.com/Belka1000867/Dagger2Kotlin

Categories

Resources