FirebaseUI with dependency injection - android

I am using dagger for dependency injection, and all my activities are dagger injected:
#Suppress("unused")
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
}
So when I use FirebaseUI, I understandably get the error for FirebaseUI activities
No injector factory bound for Class < com.firebase.ui.auth.KickoffActivity >
Is there any work around?

Add these lines to your activity module class.
#PerActivity
#ContributesAndroidInjector
internal abstract fun contributeKickoffActivity(): KickoffActivity
#PerActivity
#ContributesAndroidInjector
internal abstract fun contributeAuthMethodPickerActivity(): AuthMethodPickerActivity

Related

Activity cannot be provided without an #Inject constructor or an #Provides-annotated method | Migrating from Dagger2 to Hilt

So I'm trying to migrate my whole app from dagger to hilt and apparently I'm unable to inject activities and fragments.
My Activities:
#AndroidEntryPoint()
class MainActivity : AuthorizedFlowActivity<ActivityMainBinding>(), MainActivityUiEventHandler,
MainActivityBottomSheetBehavior,
MainActivityOpenDrawer {
My fragments:
#AndroidEntryPoint
class ProfileFragment : BaseFragment<FragmentCaptainProfileBinding>() {
My Application Class:
#HiltAndroidApp
class App : Application() {
Injecting my activities like this:
class SplashActivityNavigatorImpl #Inject constructor(
splashActivity: SplashActivity,
private val preferences: JameelPreferences
)
And it's throwing this error:
error: [Dagger/MissingBinding] com.abc.presentation.main.activities.MainActivity cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
Project Level Gradle:
classpath "com.google.dagger:hilt-android-gradle-plugin:$libVersion.hiltVersion"
App Level Gradle (all 3 modules):
id("dagger.hilt.android.plugin")
implementation "com.google.dagger:hilt-android:$libVersion.hiltVersion"
kapt "com.google.dagger:hilt-android-compiler:$libVersion.hiltCompilerVersion"
Could it be that my activities and fragments are extending bases which cannot be annotated by the #AndroidEntryPoint because they have type parameters?? HELP!
Also with dagger2, I was using interfaces like:
#Module
abstract class AppActivitiesInjector {
#ActivityScope
#ContributesAndroidInjector(modules = [RiderInviteFriendFragmentsInjector::class, RiderInviteActivityModule::class, BaseActivityModule::class])
abstract fun provideRiderInviteFriendActivity(): RiderInviteFriendActivity
#ActivityScope
#ContributesAndroidInjector(modules = [OffersActivityFragmentsInjector::class, OffersActivityModule::class, BaseActivityModule::class])
abstract fun provideOffersActivity(): OffersActivity
#ActivityScope
#ContributesAndroidInjector(modules = [TripsHistoryActivityFragmentsInjector::class, TripsHistoryActivityModule::class, BaseActivityModule::class])
abstract fun provideTripsActivity(): TripsHistoryActivity
#ActivityScope
#ContributesAndroidInjector(modules = [RiderProfileActivityFragmentsInjector::class, RiderProfileActivityModule::class, BaseActivityModule::class])
abstract fun provideRiderProfileActivity(): RiderProfileActivity
#ActivityScope
#ContributesAndroidInjector(modules = [PaymentActivityModule::class, PaymentActivityFragmentsInjector::class, BaseActivityModule::class])
abstract fun providePaymentActivity(): PaymentActivity
}
And it was working, but hilt is supposedly to reduce such boiler plate? right?
Thanks
As the error says: There is nothing that supports providing the activities and fragments. The Entry point annotations means just: "inject here". So dagger will generate everything to support "what needs to be injected in the fragments and activities". You need to provide somehow the activity. Try checking these answers:
Provide Activity instance with Hilt
How to Bind/Provide Activity or Fragment with Hilt?

Dagger/MissingBinding but module exists

I'm trying to put dagger into my project and I'm facing a compilation issue I don't get since I've done everything like the android developer tutorial. I get:
error: [Dagger/MissingBinding] INotificationService cannot be provided without an #Provides-annotated method
Here is my app annotation:
#HiltAndroidApp
class App : MultiDexApplication() {
Activity annotation:
#AndroidEntryPoint
class MainActivity: AppCompatActivity() {
Fragment annotation:
#AndroidEntryPoint
class NotificationFragment: Fragment(R.layout.fragment_notification) {
I think everything's ok until there. Then the problem I'm facing is here:
Viewmodel class:
#HiltViewModel
class NotificationViewModel #Inject constructor(private val notificationService: INotificationService): ViewModel()
Here is the interface for INotificationService:
interface INotificationService {
fun refreshNotification(): Single<List<INotification>>
fun markAsRead(notification: INotification)
}
and the implementation:
class NotificationServiceImpl #Inject constructor(#ApplicationContext context: Context): INotificationService
with associated module:
#Module
#InstallIn(ActivityComponent::class)
abstract class NotificationModule {
#Binds
abstract fun bindNotificationService(impl: NotificationServiceImpl): INotificationService
}
The bindNotificationService binds function from the module is greyed out, it's not the case on the android developer tutorial and the error makes me think I missed something to make this function findable at compile time but since there is #Module and #InstallIn(ActivityComponent::class) I have absolutly no idea why it doesn't compile.
INotificationService is ViewModel dependency and it should bind with ViewModel lifecycle so instead of this
#Module
#InstallIn(ActivityComponent::class)
abstract class NotificationModule {
#Binds
abstract fun bindNotificationService(impl: NotificationServiceImpl): INotificationService
}
Use this:-
#Module
#InstallIn(ViewModelComponent::class)
abstract class NotificationModule {
#Binds
abstract fun bindNotificationService(impl: NotificationServiceImpl): INotificationService
}

Dagger: cannot be provided without an #Inject constructor or an #Provides-annotated method

I'm attempting to inject the context of my MainActivity into a method. This is a simplified version of my Dagger setup:
AppComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
MainActivityModule::clas
]
)
interface AppComponent : AndroidInjector<MyApp> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
AppModule
class AppModule {
#Singleton
#Provides
fun provideRepository(context: Context) = Repository(context)
}
MainActivityModule
#Suppress("unused")
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
#Binds
abstract fun bindsMainActivityContext(mainActivity: MainActivity): #ActivityContext Context
}
as you can see in provideRepository() there's an argument context that should be injected. Whenever I build the app the following error appears:
error: [Dagger/MissingBinding] app.example.myapp.MainActivity cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract interface AppComponent extends dagger.android.AndroidInjector<app.example.myapp.MyApp> {
^
A binding with matching key exists in component: app.example.myapp.injection.module.MainActivityModule_ContributeMainActivity.MainActivitySubcomponent
app.example.myapp.MainActivity is injected at
app.example.myapp.injection.module.MainActivityModule.bindsMainActivityContext(mainActivity)
#app.example.myapp.injection.module.ActivityContext android.content.Context is injected at
app.example.myapp.injection.module.AppModule.provideTokenRepository(…, context)
app.example.myapp.repository.interfaces.ITokenRepository is injected at
app.example.myapp.ui.signin.SignInViewModel(repository)
app.example.myapp.ui.signin.SignInViewModel is injected at
app.example.myapp.injection.module.ViewModelModule.bindSignInViewModel(signInViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
app.example.myapp.injection.ViewModelFactory(creators)
app.example.myapp.injection.ViewModelFactory is injected at
app.example.myapp.injection.module.ViewModelModule.bindViewModelFactory(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
app.example.myapp.MainActivity.viewModelFactory
app.example.myapp.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [app.example.myapp.injection.AppComponent → app.example.myapp.injection.module.MainActivityModule_ContributeMainActivity.MainActivitySubcomponent]
The following other entry points also depend on it:
dagger.android.AndroidInjector.inject(T) [app.example.myapp.injection.AppComponent → app.example.myapp.injection.module.FragmentModule_ContributeMainFragment.MainFragmentSubcomponent]
dagger.android.AndroidInjector.inject(T) [app.example.myapp.injection.AppComponent → app.example.myapp.injection.module.FragmentModule_ContributeSignInFragment.SignInFragmentSubcomponent]
As you can see from the error Dagger seems to be able to trace from the context to the root of the app but for some reason I get the above error.
Is there anything I have done wrong? Thanks
You're trying to #Binds MainActivity from a module which you add to your AppComponent. That won't work, because there is no MainActivity (as the error states).
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
// there is no MainActivity here (in AppComponent)
#Binds
abstract fun bindsMainActivityContext(mainActivity: MainActivity): #ActivityContext Context
}
It seems like what you wanted to do is bind MainActivity as Context in your MainActivity's Subcomponent, e.g. like the following. You'll need a second module which you then add to the subcomponent:
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityModule::clas // bind module with subcomponents instead, see next
]
)
interface AppComponent // ...
// Activity module that you can add to AppComponent with subcomponents
#Module
abstract class ActivityModule {
// bind the module with the bindings here
#ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun contributeMainActivity(): MainActivity
}
// now we bind it to the MainActivity Subcomponent _only_ and it will work
#Module
abstract class MainActivityModule {
#Binds
abstract fun bindsMainActivityContext(mainActivity: MainActivity): #ActivityContext Context
}
According to the error, dagger dont know how to provide this dependency, either you have missed #Inject annotation in dependency or you might have forgot to provide dependency with #Provides annotation

dagger2 and android: load module which injects viewmodel on a map

I've started using Dagger2, so there's still a lot to learn. I'm wondering if someone could point me on the right direction.
So, I've created a module for registering the view models used by my activities. It looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
ViewModelKey is a simple helper annotation class which looks like this:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey (val value: KClass<out ViewModel>) {
}
The ViewModelModule is loaded by my main app component (used for creating the app):
#Singleton
#Component(
modules=[
AndroidSupportInjectionModule::class,
AppModule::class,
DatabaseModule::class,
NewsServiceModule::class,
JobBindingModule::class,
ViewModelModule::class,
PreferencesModule::class,
ActivityBindingModule::class
]
)
interface AppComponent: AndroidInjector<MyApp> {
#Component.Builder
abstract class Builder: AndroidInjector.Builder<MyApp>()
}
And here's the code for the ActivityBindingModule, responsible for setting up the subcomponents (in this case, activities used by my app):
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Internally, each activity is instantiating the view model with code that looks like this (called from within the onCreate method):
//view model code
_viewModel = ViewModelProviders.of(this, viewModelFactory)[ShowDetailsViewModel::class.java]
And, as you'd expect, viewModelFactory is injected as field:
#Inject lateinit var viewModelFactory: ViewModelProvider.Factory
Both view models have external dependencies which are set up on the other modules referenced by the top app component.
And, for the sake of completeness, here's the code for my view model factory:
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T
= viewModels[modelClass]?.get() as T
This code works, but it seems like it can be improved. After reading the docs, I'm under the impression that I could refactor my ViewModeModule so that it will simply instantiate my ViewModelFactory and move each of the view model declarations into separate module (so that each of them can be injected only in the "correct" activity).
In order to test this, I've started by moving the ShowDetailsViewModel into a new module which has only one entry:
#Module
internal abstract class DetailsModule {
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
After that, the ViewModelModule looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
}
And I've updated the ActivityBindingModule so that in looks like this:
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [DetailsModule::class])
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Notice that now I'm passing the DetailsModule (which instantiates the ShowDetailsViewModel) to the ContributeAndroidInjector annotation which is applied to the showDetailsActivity method because that view model is only used by that activity.
Now, I'm surely missing something because after doing this, I'm always getting the following exception:
java.lang.IllegalStateException: ViewModelProviders.of(th…ilsViewModel::class.java] must not be null
If I debug the app, I can see that moving the ShowDetailsViewModel into its own model does not register it on the map used by the factory (ie, the map has only one entry, which corresponds to the MainActivityViewModel that is registered in the ViewModelModule.
I thought that moving each view model the declaration into each a module used by a subcomponent should still allow it to be registered in a map injected by a module which is registered with the top component. Am I wrong? What is it that I'm missing to make this work?
Thanks.
The problem lies with ViewModelFactory being #Singleton and that it won't get any of the bindings you add in your subcomponents. Remove the scope from your factory or make it #ActivityScoped (the same scope as the ViewModel for the Activity)
The Activity (#ActivityScoped) has access to the factory (#Singleton), but the factory (#Singleton) does not have access to use or create the ViewModel from a lower scope (#ActivityScoped). So moving the factory to the same scope (#ActivityScoped) would give it access to create the viewmodel in question.

Dependency injection in non-global activity scope done right. How to provide special MyActivity as Activity

I am using dependency injection to provide global objects (#Singleton) and non global objects for activities only (#ActivityScoped).
Now I wonder if I did it right and if it could be done better. The most interesting part of this DI implementation is the injection of the object SomeManager into 2 different activities with restricted scope
Here is the code
The main app component
#Singleton
#Component(modules = [
ApplicationModule::class,
AndroidSupportInjectionModule::class,
ActivityModule::class,
ManagerModule::class,
...
ClientModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): AppComponent.Builder
fun build(): AppComponent
}
}
The appclication module
#Module
abstract class ApplicationModule {
#Binds
#Singleton
internal abstract fun bindContext(application: Application): Context
}
The module for the activities
#Module
abstract class ActivityModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [LoginActivityModule::class])
internal abstract fun loginActivity(): LoginActivity
}
And now I want to inject a new SomeManager to the LoginActivity and a new one to the MainActivity.
The approach is having a module for each activity like you see above in the #ContributesAndroidInjector(modules... annotation. The implementations of the 2 files look like this
#Module
object MainActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: MainActivity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
And
#Module
object LoginActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: LoginActivity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
The Question:
1) Now the LoginActivityModule and MainActivityModule look very similar. Is there a better approach to provide SomeManager to both activities without making them #Singleton and withouth creating a module for each activity (becaues SomeManager only needs an Activity, not a special sublcass)? I had something in mind like that it only takes an Activity instead of a specific XXXActivity. But how can I tell dagger to provide the XXXActivity as Activity
2) And beside that optimizing in 1), is this a correct implementation?
Update 1
I have solved it by the following implementation. Is this the right way to do this?
Module that provides the MainActivity as Activity
#Module
object MainActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideAsActivity(activity: MainActivity): Activity = activity
}
Module that provides the MainActivity as Activity
#Module
object LoginActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideAsActivity(activity: LoginActivity): Activity = activity
}
A Manager module that is only #ActivityScoped
#Module
object ManagerModule2 {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: Activity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
And the Android injector for the activities
#Module
abstract class ActivityModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [ManagerModule2::class, MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [ManagerModule2::class, LoginActivityModule::class])
internal abstract fun loginActivity(): LoginActivity
}
Now the LoginActivityModule and MainActivityModule look very similar. Is there a better approach to provide SomeManager to both activities without making them #Singleton? I had something in mind like that it only takes an Activity instead of a specific XXXActivity.
Yes, you could do that. You can replace the dependency on the specific Activity and replace it with Activity or Context (depending on your actual needs) and move that declaration into a separate module, which you could include in both of your ActivityModules.
#Module
object SomeManagerModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: Activity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
And either include it with the module or add it to ContributesAndroidInjector.
#Module(includes = [SomeManagerModule::class])
object MainActivityModule { /* ... */ }
// or
#ActivityScoped
#ContributesAndroidInjector(modules = [LoginActivityModule::class, SomeManagerModule::class])
internal abstract fun loginActivity(): LoginActivity
And you could even remove the need for a module completely by using Constructor Injection.
#ActivityScoped
SomeManager #Inject constructor(activity: Activity, apiClient: ApiClient)
Either way you would have to bind / provide your xxxActivitys as a Activity somewhere so that Dagger can find them.
And beside that optimizing in 1), is this a correct implementation?
Looks good to me. You said you wanted a new manager per Activity, so #ActivityScoped seems the correct choice. You could possibly remove the scope completely if you don't have to ensure that there is only ever one per Activity-scoped component, but this depends on your exact usecase.

Categories

Resources