How to #Inject an abstract BaseActivity and its subclasses using dagger.android? - android

I'm using the new dagger.android package from Dagger 2 to inject Android dependencies in my project.
I need all my Activities to be a subclass of an abstract BaseActivity
In my BaseActivity I have member variables to be injected. This way:
abstract class BaseActivity : AppCompatActivity() {
#Inject
lateinit var prefs: MyPreferenceDataStore
...// more #Injected members
}
I do it because I want subclasses of BaseActiviy can have access to injected members of BaseActivity:
class SubClassActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle) {
val x = prefs.getXXX //use prefs variable from parent class
}
}
This is my ApplicationComponent:
#Singleton #Component(modules = arrayOf(
ApplicationModule::class,
ActivityBindingModule::class,
AndroidSupportInjectionModule::class
))
interface ApplicationComponent {
#Component.Builder interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
fun inject(app: AndroidApplication)
}
The ApplicationModule class has simple #Provides annotated methods:
#Module
class ApplicationModule {
#Singleton #Provides
fun providesMyPreferenceDataStore(context: Context): MyPreferenceDataStore {
return MyPreferenceDataStoreImpl(context)
}
// more #Provides annotated methods
}
I think the problem is in my ActivityBindingModule
#Module
abstract class ActivityBindingModule {
#PerActivity
#ContributesAndroidInjector(
modules = arrayOf(BaseActivityModule::class
))
abstract fun bindBaseActivity(): BaseActivity
#PerActivity
#ContributesAndroidInjector(
modules = arrayOf(
BaseActivityModule::class
))
abstract fun bindSubClassActivity(): SubClassActivity
}
This is what I have tried so far:
Make the bindSubClassActivity() method not to depend of BaseActivityModule::class, didn't work.
Move the providesMyPreferenceDataStore from ApplicationModule to the BaseActivityModule, so that the class is:
#Module
class BaseActivityModule {
#PerActivity #Provides
fun providesMyPreferenceDataStore(context: Context): MyPreferenceDataStore {
return MyPreferenceDataStoreImpl(context)
}
}
And this is the error I'm getting:
Error: [dagger.android.AndroidInjector.inject(T)] com.example.BaseActivity cannot
be provided without an #Provides-annotated method.
This type supports members injection but cannot
be implicitly provided.

I didn't understand exactly what you try to do but this solutions based in what i understand
AppComponent should look like this
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityModule::class])
interface AppComponent : AndroidInjector<DaggerApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
your base activity which will inject all the objects
abstract class BaseActivity : DaggerAppCompatActivity() {
#Inject
lateinit var prefs: SharedPreferences
//other objects to inject
}
The activity that will inherit from it eg:MainActivity
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
prefs.getBoolean("s", true)
}
}
And Activity module
#Module
abstract class ActivityModule {
#ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
#ContributesAndroidInjector
abstract fun bindBaseActivity():BaseActivity
}
AppModule
#Module
class AppModule {
#Singleton
#Provides
fun providesMyPreferenceDataStore(application: Application): SharedPreferences {
return application.getSharedPreferences("test", Context.MODE_PRIVATE)
}
}

Related

Field injection in AppWidgetProvider in kotlin using dagger 2

I am using dagger2 and kotlin in my project. I have injected activity and viewmodels and now I want to inject appwidgetprovider class for app widgets. I can`t find a way to inject fields in to appwidgetprovider class. Here is my dagger2 implementaion.
this is App Component class
#Singleton
#Component(
modules = [
UserInformationModule::class,
AndroidInjectionModule::class,
AppModule::class,
MainActivityModule::class,
ServiceBuilderModule::class]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(application: BaseClass)
}
This is AppModule class
#Module(includes = [ViewModelModule::class, CoreDataModule::class])
class AppModule {
#Singleton
#Provides
fun provideMyMyAppService(
#MyAppAPI okHttpClient: OkHttpClient,
converterFactory: MoshiConverterFactory
) = provideService(okHttpClient, converterFactory, MyMyAppApi::class.java)
#MyAppAPI
#Provides
fun providePrivateOkHttpClient(
upstreamClient: OkHttpClient
): OkHttpClient {
return upstreamClient.newBuilder().build()
}
#Singleton
#Provides
fun provideRemoteDataSource(myMyAppService: MyMyAppApi) = RemoteDataSource(myMyAppService)
#Singleton
#Provides
fun provideDb(app: Application) = AppDatabase.getInstance(app)
//other code
This is Fragment Builder Module
#Suppress("unused")
#Module
abstract class FragmentBuildersModule {
#ContributesAndroidInjector
abstract fun homeFragment(): HomeFragment
#ContributesAndroidInjector
abstract fun fragHome(): FragHome
//other code
}
this is my Main Activity Module
#Suppress("unused")
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeMainActivity(): HomeActivity
#ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeSplashActivity(): SplashActivity
}
This is my ViewModel Module
#Suppress("unused")
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel
///other code
}
I tried to inject appwidgetprivider class using
AndroidInjection.inject(this)
as I did in Service. But this method only excepts Activity, Fragment, service, broadcast receiver and contentproviders. Any help please.
I am using dagger 2.23.2 and kotlin 1.3.41
Appwidget provicer can be injected the same way a broadcast receiver is injected.
By looking at your provided code you can do some thing like this.
Create an abstract function
#ContributesAndroidInjector
internal abstract fun contributeWidget(): YourWidgetClass
extend your Baseclass with HasBroadcastReceiverInjector and implement broadcastReceiverInjector
#Inject
lateinit var broadcastReceiverInjector: DispatchingAndroidInjector<BroadcastReceiver>
override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> {
return broadcastReceiverInjector
}
and fillany inject in the widgetprovider class in onreceive
before super call
AndroidInjection.inject(this, context)

Dagger injection ViewModel Factory compile time error (dependency cycle in dagger)

I receive following error message from dagger at compile time. It finds a dependency cycle in dagger :
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sample.android.storytel.StorytelApplication> {
^
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sample.android.storytel.ui.DetailFragment.factory
com.sample.android.storytel.ui.DetailFragment is injected at
com.sample.android.storytel.di.DetailModule.providePost$app_debug(fragment)
com.sample.android.storytel.domain.Post is injected at
com.sample.android.storytel.viewmodels.DetailViewModel.Factory(…, post)
com.sample.android.storytel.viewmodels.DetailViewModel.Factory is injected at
com.sample.android.storytel.di.DetailModule.bindViewModelFactory$app_debug(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sample.android.storytel.ui.DetailFragment.factory
com.sample.android.storytel.ui.DetailFragment is injected at
dagger.android.AndroidInjector.inject(T) [com.sample.android.storytel.di.AppComponent → com.sample.android.storytel.di.ActivityBindingModule_MainActivity$app_debug.MainActivitySubcomponent → com.sample.android.storytel.di.DetailModule_DetailFragment$app_debug.DetailFragmentSubcomponent]
Here is my DetailModule :
#Module
abstract class DetailModule {
#ContributesAndroidInjector
internal abstract fun detailFragment(): DetailFragment
#Binds
internal abstract fun bindViewModelFactory(factory: DetailViewModel.Factory): ViewModelProvider.Factory
#Module
companion object {
#Provides
#JvmStatic
internal fun providePost(fragment: DetailFragment): Post =
DetailFragmentArgs.fromBundle(fragment.arguments!!).post
}
}
Here is AppComponent :
#Singleton
#Component(
modules = [ActivityBindingModule::class,
AndroidSupportInjectionModule::class,
Network::class,
BaseModule::class]
)
interface AppComponent : AndroidInjector<StorytelApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
Here is ActivityBindingModule :
#Module
abstract class ActivityBindingModule {
#ContributesAndroidInjector(
modules = [MainModule::class,
DetailModule::class]
)
internal abstract fun mainActivity(): MainActivity
}
And this is my DetailViewModel Factory :
/**
* Factory for constructing DetailViewModel with parameter
*/
class Factory #Inject constructor(
private val useCase: DetailUseCase,
val post: Post
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(DetailViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return DetailViewModel(useCase, post) as T
}
throw IllegalArgumentException("Unable to construct viewmodel")
}
}
I have injected factory at my DetailFragment :
class DetailFragment #Inject
constructor() // Required empty public constructor
: DaggerFragment() {
#Inject
lateinit var factory: DetailViewModel.Factory
private val viewModel: DetailViewModel by lazy {
ViewModelProviders.of(this, factory)
.get(DetailViewModel::class.java)
}
}
I change my providePost method by passing MainActivity as parameter instead of DetailFragment, and problem resolved :
#Provides
#JvmStatic
internal fun providePost(activity: MainActivity): Post {
val navHostFragment = activity.supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment)
val fragment = navHostFragment?.childFragmentManager?.fragments?.get(0);
return DetailFragmentArgs.fromBundle(fragment?.arguments!!).post
}

dagger.internal.codegen.ComponentProcessor was unable to process this interface because not all of its dependencies could be resolved

I've been trying to setup dagger in a multimodule project. My setup is the following:
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class, NetworkModule::class, MovieListActivityModule::class, DetailActivityModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MoviesApplication)
}
#Module
class NetworkModule { .... }
#Module
abstract class MovieListActivityModule {
#ContributesAndroidInjector(modules = [ListFragmentModule::class, ListNetworkModule::class, ListViewModelsModule::class])
abstract fun bindMovieListActivity(): MovieListActivity
}
#Module
abstract class ListFragmentModule {
#ContributesAndroidInjector
abstract fun bindListFragment(): ListFragment
}
#Module
public class ListNetworkModule {
#Provides
SearchService providesSearchService(Retrofit retrofit) {
return retrofit.create(SearchService.class);
}
}
#Module
abstract class ListViewModelsModule {
#Binds
abstract fun bindDaggerViewModelFactory(daggerViewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(ListViewModel::class)
abstract fun bindListViewModel(listViewModel: ListViewModel): ViewModel
}
#Module
abstract class DetailActivityModule {
#ContributesAndroidInjector(modules = [DetailViewModelsModule::class, DetailNetworkModule::class, DbModule::class])
abstract fun bindDetailActivity(): DetailActivity
}
#Module
abstract class DetailViewModelsModule {
#Binds
abstract fun bindDaggerViewModelFactory(daggerViewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(DetailsViewModel::class)
abstract fun bindDetailViewModel(detailsViewModel: DetailsViewModel): ViewModel
}
#Module
public class DetailNetworkModule {
#Provides
DetailService providesDetailService(Retrofit retrofit) {
return retrofit.create(DetailService.class);
}
}
#Module
class DbModule {
#Provides
fun providesFavouriteMovieDb(application: Application): FavMovieDb = FavMovieDb.getDatabase(application)
}
class DaggerViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>)
: ViewModelProvider.Factory {.........}
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
class App : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingActivityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this)
}
override fun activityInjector(): DispatchingAndroidInjector<Activity>? = dispatchingActivityInjector
}
Till this code everything was ok and works perfectly. But problem appears when I add a new FavouriteActivityModule in AppComponent. Like below:
#Module
abstract class FavouriteViewModelModule {
#Binds
abstract fun bindDaggerViewModelFactory(daggerViewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(FavouriteViewModel::class)
abstract fun bindFavouriteViewModel(viewModel: FavouriteViewModel): ViewModel
}
#Module
abstract class FavouriteActivityModule {
#ContributesAndroidInjector(modules = [FavouriteViewModelModule::class, DbModule::class])
abstract fun bindFavouriteActivity(): FavoriteActivity
}
Final AppComponent looks like this (added FavouriteActivityModule::class):
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class, NetworkModule::class, MovieListActivityModule::class, DetailActivityModule::class, FavouriteActivityModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MoviesApplication)
}
After adding FavouriteActivityModule::class dagger is no more building and complaining like below:
e: /Users/...../di/AppComponent.java:8: error: [ComponentProcessor:MiscError] dagger.internal.codegen.ComponentProcessor was unable to process this interface because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code.
public abstract interface AppComponent {
^
I've not figured out the reason yet and already spent whole day finding out the reason of this error. Any help? TIA

ViewModel cannot be provided without an #Inject constructor or an #Provides-annotated

Question EDITED
I am injecting ViewModelProvider.Factory to BaseActivity like below
open class BaseActivity : DaggerAppCompatActivity() {
#Inject
lateinit var factories: ViewModelProvider.Factory
inline fun <reified T : ViewModel> getViewModel(): T {
return ViewModelProvider(this, factories).get(T::class.java)
}
}
viewModel only works when we inject then like below.
class MainViewModel #Inject constructor( private val alertStore: AlertStore)
: BaseViewModel(){
fun showDialog(){
viewModelScope.launch {
delay(4000)
alertStore.showToast("Alert after 4 seconds.")
}
}
}
Why this #Inject constructor is necessary in my current implementation
class MainActivity : BaseActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = getViewModel()
viewModel.showDialog()
}
}
App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().addContext(this).build()
}
}
AppComponent.kt
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ViewModelInjector::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
fun provideViewModelFactories(viewModels: Map<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>):
ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val factory = viewModels[modelClass]?.get() ?: error(
"No factory provided against ${modelClass.name}"
)
#Suppress("UNCHECKED_CAST")
return factory as T
}
}
}
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
//#Scope("")
#ContributesAndroidInjector ///(modules = {MainModelFactory.class})
public abstract MainActivity bindMainActivity();
}
ViewModelInjector.kt
#Module
public abstract class ViewModelInjector {
#Binds
#IntoMap
#ViewModelKey(MainViewModel.class)
public abstract ViewModel providesMainViewModel(MainViewModel model);
}
ViewModelKey.kt
#MapKey
#Retention(AnnotationRetention.SOURCE)
annotation class ViewModelKey(
val value: KClass<out ViewModel>
)
Why do I have to append #Inject constructor to each ViewModel and kindly explain a little why we need #Binds #IntoMap and with ViewModel
When you use dagger android you should make your activities and fragments as extensions of DaggerActivity (and DaggerFragment for fragments).
class MainActivity : DaggerActivity() {
#Inject
lateinit var viewModel: MainViewModel
}
Next you should prepare infrastructure for injection:
Create injectors for each your activity:
// All your injectors can be defined in this module
#Module(includes = [AndroidInjectionModule::class])
interface AppInjectorModule {
#ContributesAndroidInjector(modules = [MainActivityVmModule::class, /*other dependecies*/])
fun getMainActivityInjector(): MainActivity
}
Create modules to provide view models (can be multiple for one activity) and factory
#Module(includes = [VmFactoryModule::class])
abstract class MainActivityVmModule {
// bind implementation of ViewModel into map for ViewModelFactory
#Binds
#IntoMap
#ClassKey(MainViewModelImpl::class)
abstract fun bindMainVm(impl: MainViewModelImpl): ViewModel
#Module
companion object {
#Provides
#JvmStatic
fun getMainVm(activity: MainActivity, factory: ViewModelProvider.Factory): MainViewModel {
// create MainViewModelImpl in scope of MainActivity and inject dependecies by ViewModelFactory
return ViewModelProviders.of(activity, factory)[MainViewModelImpl::class.java]
}
}
}
Factory can be provided by different module to avoid duplication
#Module
interface VmFactoryModule {
#Binds
// bind your implementation of factory
fun bindVmFactory(impl: ViewModelFactory): ViewModelProvider.Factory
}
Add activities injectors to AppComponent graph
#Component(
modules = [
AppInjectorModule::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App>
Additional info: Dagger & Android

Dagger 2 problem in providing interface implementation

I'm so new to Dagger and I have some error when I want to inject an interface implementation, I have an interface called Mapper<K,V> and it's implementation is ArticleEntityArticleMapper
error: [Dagger/MissingBinding]ir.siatech.newsappkotlinclean.domain.commons.Mapper<? superir.siatech.newsappkotlinclean.domain.entities.ArticleEntity,ir.siatech.newsappkotlinclean.presentation.entities.Article> cannot be provided without an #Provides-annotated method.
And here is my codes:
#Module
abstract class MapperModule {
#Binds
abstract fun providesArticleEntityArticleMapper(articleEntityArticleMapper: ArticleEntityArticleMapper)
: Mapper<ArticleEntity, Article>
}
#Module(includes = [ViewModelModule::class])
abstract class ViewModelBuilder {
#Binds
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
}
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(NewsViewModel::class)
abstract fun bindNewsViewModel(newsViewModel: NewsViewModel): ViewModel
}
and here is my ViewModel :
class NewsViewModel #Inject constructor() : BaseViewModel() {
#Inject
lateinit var articleEntityArticleMapper: Mapper<ArticleEntity, Article>
}
EDIT :
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
UseCaseModule::class,
MapperModule::class,
DataModule::class,
ActivityBuilder::class,
ContextModule::class,
NetworkModule::class]
)
interface AppComponent : AndroidInjector<NewsApp> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}

Categories

Resources