Good day all,
I would like to provide application context for my AppModule class.
I would like to have a PrefsHelper be provided through out the application like I do with my ApiService class.
The code for my AppModule:
#Module
#Suppress("unused")
object AppModule {
#Provides
#Reusable
#JvmStatic
internal fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
/**
* Provides the Retrofit object.
* #return the Retrofit object
*/
#Provides
#Reusable
#JvmStatic
internal fun provideRetrofitInterface(): Retrofit {
val interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
val client: OkHttpClient = OkHttpClient.Builder().apply { this.addInterceptor(interceptor) }.build()
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create())
.build()
}
The way I have seen it done before (In Java) is create a constructor and pass in the application context in that way. Kotlin doesnt allow that with an object
How can I have the context be provided in this class allowing me to provide PrefsHelper?
You could also use the BindsInstance annotation in your AppComponent.
So your AppComponent would look something like this:
#Singleton
#Component(modules = YOUR_MODULES)
interface AppComponent {
//Whatever injections you have
#Component.Builder
interface Builder {
fun build(): AppComponent
#BindsInstance
fun application(Application application): Builder
}
}
Then you just add the new methods to your AppComponent creation in your Application class.
DaggerAppComponent.builder().application(this).build()
Change your AppModule to something like this:
#Module
class AppModule(private val application: Application) {
#Singleton
#Provides
internal fun provideApplication(): Application = application
#Singleton
#Provides
internal fun providePrefs(application: Application): YourPref {
return YourPref(application)
}
}
Related
I wrote network module in project. I checked all similiar codes and qustions there are some people have same problem. But they all didn't solve this problem for me. I don't know what is problem here.
This is the error:
error: #Provides methods can only be present within a #Module or #ProducerModule
public static final com.technoface.iga.api.BoardingPassService postBoardingPassesRead(#org.jetbrains.annotations.NotNull()
NetworkModule.kt
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Singleton
#Provides
fun provideBaseUrl() = BuildConfig.BASE_URL
#Provides
#Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
#ServiceInterceptorOkHttpClient
#Singleton
#Provides
fun providesServiceInterceptor(clientPreferences: ClientPreferences): ServiceInterceptor =
ServiceInterceptor(clientPreferences)
#Provides
#Singleton
fun provideOkHttpClient(
logging: HttpLoggingInterceptor,
#ServiceInterceptorOkHttpClient
serviceInterceptor: ServiceInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.addInterceptor(logging)
.addInterceptor(serviceInterceptor)
.build()
}
#Provides
#Singleton
fun provideNetworkResultCallAdapterFactory(): CallAdapter.Factory {
return NetworkResultCallAdapterFactory.create()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, callAdapterFactory: CallAdapter.Factory): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(callAdapterFactory)
.client(client)
.build()
}
#Provides
#Singleton
fun postBoardingPassesRead(serviceBuilder: ServiceBuilder): BoardingPassService = serviceBuilder.buildService(BoardingPassService::class.java)
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class BaseUrl
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class ServiceInterceptorOkHttpClient
BoardingPassServiceRepo.kt
class BoardingPassServiceRepo #Inject constructor(
private val boardingPassService: BoardingPassService
) {
suspend fun postBoardingPassesRead(boardingPassInfoRequest: BoardingPassInfoRequest) = boardingPassService.postBoardingPassesRead(
encryptData(boardingPassInfoRequest)
)
}
AppModule.kt
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton
#Provides
fun provideClientPreferences(
#ApplicationContext context: Context
) = ClientPreferences(context)
}
class BoardingPassServiceRepo #Inject constructor(
private val boardingPassService: BoardingPassService
) {
suspend fun postBoardingPassesRead(boardingPassInfoRequest: BoardingPassInfoRequest) = boardingPassService.postBoardingPassesRead(
encryptData(boardingPassInfoRequest)
)
}
Update Gradle version.
Changed gradle version.
Clean, Rebuild, Invalidate cache
Deactivated Antivirus.
I have implemented Hilt on my app. I am getting this error for the first time. Does anyone have an idea how to go about it?
weatherstackapp/utils/BaseApplication_HiltComponents.java:128: error:
[Dagger/MissingBinding]
com.malinikali.weatherstackapp.utils.BaseApplication cannot be provided
without an #Inject constructor or an #Provides-annotated method.
public abstract static class SingletonC implements
BaseApplication_GeneratedInjector,
^
Below is my BaseApplication class
#HiltAndroidApp
class BaseApplication : Application()
Below is my AppModule class
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
private val client = OkHttpClient.Builder().apply {
addInterceptor(ApiInterceptor())
}.build()
#Singleton
#Provides
fun provideContext(application: BaseApplication): Context {
return application.applicationContext
}
#Provides
fun providesBaseUrl() = Constants.BASE_URL
#Provides
#Singleton
fun provideRetrofitInstance(BASE_URL:String):ApiService =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(client)
.build()
.create(ApiService::class.java)
}
Hilt injects Application and #ApplicationContext Context by default. If this is the only binding that uses BaseApplication, you can simply change the parameter to one of these default bindings instead:
#Singleton
#Provides
fun provideContext(application: Application): Context {
return application.applicationContext
}
If you really need a BaseApplication binding, you can create a #Provides method based on one of the included bindings:
#Provides
fun provideBaseApplication(application: Application): BaseApplication {
return application as BaseApplication
}
I've just refactored my dagger code to make it scalable and move all core stuff to a separate module called di.
Now when I try to inject my dependencies in the app module I got this :
[Dagger/MissingBinding] retrofit2.Retrofit cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface ResetPasswordComponent {
^
retrofit2.Retrofit is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordNetworkModule.providerResetPasswordAPI(retrofit)
com.sahra.oms.ibshop.data.remote.service.ResetPasswordService is injected at
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl(resetPasswordService)
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueRepositoryImpl is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindUniqueIdRepository(uniqueRepositoryImpl)
com.sahra.oms.ibshop.data.repisotory.nationalid.UniqueIdRepository is injected at
com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel(repository)
com.sahra.oms.ibshop.features.resetpassword.uniqueid.CheckUniqueIDViewModel is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordModule.bindCheckIdViewModel(checkUniqueIDViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.sahra.oms.ibishop.di.util.ViewModelFactory(viewModelsMap)
com.sahra.oms.ibishop.di.util.ViewModelFactory is injected at
com.sahra.oms.ibshop.di.ViewModelBuilder.bindViewModelFactory(arg0)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment.viewModelFactory
com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment is injected at
com.sahra.oms.ibshop.features.resetpassword.di.ResetPasswordComponent.inject(com.sahra.oms.ibshop.features.resetpassword.newpassword.NewPasswordFragment)
ResetPasswordService is just a Retrofit interface.
Here is my code:
AppComponent
#Singleton
#AppScope
#Component
interface AppComponent {
fun provideContextComponent(): ContextComponent
fun provideNetworkComponent(): NetworkComponent
fun provideSharedPrefComponent(): SharedPreferencesComponent
fun inject(app: Application)
#Component.Factory
interface Factory {
fun create(
#BindsInstance
context: ContextComponent,
#BindsInstance
network: NetworkComponent,
#BindsInstance
sharedPrefs: SharedPreferencesComponent
): AppComponent
}
}
NetworkComponent :
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class NetworkScope
#NetworkScope
#Component(
dependencies = [ContextComponent::class],
modules = [OkHttpModule::class, AuthBinderModule::class]
)
interface NetworkComponent {
fun provideOkHttp(): OkHttpClient
fun provideRetrofit(): Retrofit
fun provideGson(): GsonConverterFactory
}
OkHttpModule :
#Module
object OkHttpModule {
private const val BASE_URL = "base_url"
#Provides
#JvmStatic
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message -> Log.d("<<<network>>>", message) }).apply {
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
}
}
#Provides
#JvmStatic
fun provideChuckInterceptor(app: Application): ChuckInterceptor = ChuckInterceptor(app)
#Provides
#JvmStatic
fun provideOkhttpCache(app: Application): Cache =
Cache(app.cacheDir, 50_000_000)
#Provides
#NetworkScope
#JvmStatic
fun provideClient(
loggingInterceptor: HttpLoggingInterceptor,
chuckInterceptor: ChuckInterceptor,
authInterceptor: Interceptor,
cache: Cache
): OkHttpClient {
return OkHttpClient.Builder()
.cache(cache)
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.addInterceptor(chuckInterceptor)
.build()
}
#Provides
#NetworkScope
#JvmStatic
fun provideGson() = Gson()
#Provides
#JvmStatic
fun provideGsonConverter(gson: Gson) = GsonConverterFactory.create(gson)
#Provides
#NetworkScope
#JvmStatic
fun provideRetrofit(
gsonConverterFactory: GsonConverterFactory,
client: Lazy<OkHttpClient>
): Retrofit = Retrofit.Builder()
.callFactory { request -> client.get().newCall(request) }
.baseUrl(BASE_URL)
.addConverterFactory(gsonConverterFactory)
.build()
}
Here is how I try to inject the dependencies:
#FeatureScope
#Component(
dependencies = [AppComponent::class],
modules = [
ResetPasswordNetworkModule::class,
ResetPasswordModule::class,
ViewModelBuilder::class
]
)
interface ResetPasswordComponent {
fun inject(newPasswordFragment: NewPasswordFragment)
fun inject(checkUniqueIDFragment: CheckUniqueIDFragment)
#Component.Builder
interface Builder {
fun coreComponent(appComponent: AppComponent): Builder
fun build(): ResetPasswordComponent
}
}
#Module
abstract class ResetPasswordModule {
#Binds
abstract fun bindResetPasswordRepository(resetPasswordRepositoryImpl: ResetPasswordRepositoryImpl): ResetPasswordRepository
#Binds
abstract fun bindUniqueIdRepository(uniqueRepositoryImpl: UniqueRepositoryImpl): UniqueIdRepository
#Binds
#IntoMap
#ViewModelKey(CheckUniqueIDViewModel::class)
abstract fun bindCheckIdViewModel(checkUniqueIDViewModel: CheckUniqueIDViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(NewPasswordViewModel::class)
abstract fun bindNewPasswordViewModel(newPasswordViewModel: NewPasswordViewModel): ViewModel
}
#Module
object ResetPasswordNetworkModule {
#Provides
#JvmStatic
#FeatureScope
fun provideUserAPI(
retrofit: Retrofit
): ResetPasswordService = retrofit.create(ResetPasswordService::class.java)
}
and here is my repository code:
class ResetPasswordRepositoryImpl #Inject constructor(
private val resetPasswordService: ResetPasswordService
) : ResetPasswordRepository {
}
Fragment :
class NewPasswordFragment{
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
}
class NewPasswordViewModel #Inject constructor(
private val repository: ResetPasswordRepository
)
Thanks in advance.
Your app component factory looks like this:
fun create(
#BindsInstance
context: ContextComponent,
#BindsInstance
network: NetworkComponent,
#BindsInstance
sharedPrefs: SharedPreferencesComponent
): AppComponent
This provides access to an instance of NetworkComponent, so any #Provides, #Binds, or #Inject that requires a NetworkComponent can get one. It does not, however, give direct access to NetworkComponent's object graph.
NetworkComponent already exposes Retrofit, so you can certainly get one if you have access to the component. However, this process is not automatic, and in your setting requires a #Provides method.
#Provides
fun provideRetrofit(component: NetworkComponent): Retrofit = component.provideRetrofit()
This is more convoluted that it needs to be. A better way to accomplish this is to make NetworkComponent a dependency of AppComponent (or just use its modules and drop the network component entirely), then expose Retrofit in AppComponent.
// using multiple scoped dependencies requires Dagger 2.27
#AppScope
#Component(dependencies = [ContextComponent::class, NetworkComponent::class, SharedPreferencesComponent::class])
interface AppComponent {
fun provideRetrofit(): Retrofit
// ...
}
ApiModule.kt
#Module
class ApiModule {
#Provides
#Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
#Provides
#Singleton
fun provideUserApi(retrofit: Retrofit): HeroesApi {
return retrofit.create(HeroesApi::class.java)
}
#Provides
#Singleton
fun provideApiManager(): ApiManager {
return ApiManager()
}
}
ApiManager.kt
class ApiManager {
#Inject
lateinit var mRetrofit: Retrofit
fun getAllHeroes(): MutableLiveData<Result<List<Hero>>> {
val mHeroesApi = mRetrofit.create(HeroesApi::class.java)
return NetworkHandler<List<Hero>>().makeCall(mHeroesApi.getAllHeroes())
}
}
HeroesApi.kt
interface HeroesApi {
#GET("/marvel")
fun getAllHeroes(): Call<List<Hero>>
}
Error i am getting
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property mRetrofit has not been initialized
at com.hardik.repository.network.ApiManager.getAllHeroes(ApiManager.kt:17)
at com.hardik.repository.Repository.getHeroesFromNetwork(Repository.kt:16)
at com.hardik.androidtemplate.usecase.GetHeroesUseCase.execute(GetHeroesUseCase.kt:14)
at com.hardik.androidtemplate.viewmodel.HeroListViewModel.<init>(HeroListViewModel.kt:9)
Let me know for more details
You don't need to inject Retrofit instance in your ApiManager. You already have Provide method for HeroesApi in your ApiModule so you can directly pass it. First change your ApiManager with:
class ApiManager(private val mHeroesApi: HeroesApi) {
fun getAllHeroes(): MutableLiveData<Result<List<Hero>>> {
return NetworkHandler<List<Hero>>().makeCall(mHeroesApi.getAllHeroes())
}
}
Then change your ApiModule
#Module
class ApiModule {
// Rest code same as it is already
#Provides
#Singleton
// Since you already have Provide method which provides HerosApi,
// Dagger will automatically inject this below.
fun provideApiManager(herosApi: HerosApi): ApiManager {
return ApiManager(herosApi)
}
}
OR
You can simply change your ApiManager to have HorseApi injected into constructor and you won't need to have Provide method for ApiManager even. For this, change ApiManager with following:
// Notice the #Inject before constructor
class ApiManager #Inject constructor(private val mHeroesApi: HeroesApi) {
fun getAllHeroes(): MutableLiveData<Result<List<Hero>>> {
return NetworkHandler<List<Hero>>().makeCall(mHeroesApi.getAllHeroes())
}
}
and then, you can remove Provide method for ApiManager from ApiModule because your ApiManager only needs HerosApi in constructor and you have defined Provide method which returns HerosApi so dagger already knows how to construct your ApiManager.
So your ApiModule will finally look like this:
#Module
class ApiModule {
#Provides
#Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
#Provides
#Singleton
fun provideUserApi(retrofit: Retrofit): HeroesApi {
return retrofit.create(HeroesApi::class.java)
}
// There's no need for providing ApiManager.
}
I am converting one my project into kotlin source code.
While building the dagger I am encountered with this problem
cannot be provided without an #Inject constructor or an #Provides-annotated method.
I have 2 modules as below
#Module
class AppClient {
#Provides
#Singleton
fun provideHttpCache(application: Application): Cache {
val cacheSize = 10 * 1024 * 1024
return Cache(application.cacheDir, cacheSize.toLong())
}
#Provides
#Singleton
fun provideGson(): Gson {
val gsonBuilder = GsonBuilder()
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
// gsonBuilder.excludeFieldsWithoutExposeAnnotation();
return gsonBuilder.create()
}
#Provides
#Singleton
fun provideOkhttpClient(cache: Cache): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
val client = OkHttpClient.Builder()
client.cache(cache)
client.addInterceptor(interceptor)
return client.build()
}
#Provides
#Singleton
fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(NetworkConstant.BASE_URL)
.client(okHttpClient)
.build()
}
#Provides
#Singleton
fun provideApiCall(retrofit: Retrofit): NetworkCall {
return retrofit.create(NetworkCall::class.java)
}
#Provides
#Singleton
fun provideSharedPreference(application: Application): SharedPreferences {
return application.applicationContext.getSharedPreferences(
application.getString(R.string.shared_pref_name),
MODE_PRIVATE
)
}
#Provides
#Singleton
fun provideSharedPreferenceEditor(sharedPreferences: SharedPreferences): SharedPreferences.Editor {
return sharedPreferences.edit()
}
#Provides
#Singleton
fun provideAppPresenter(
sharedPreferences: SharedPreferences, editor: SharedPreferences.Editor,
apiCall: NetworkCall): AppPresenter {
return AppPresenter(sharedPreferences, editor, apiCall)
}
}
And the second module
#Module
class AppModule(val application: MyApplication) {
#Provides
#Singleton
internal fun provideApplication(): MyApplication {
return application
}
}
Application class
class MyApplication : Application() {
lateinit var mApiComponent: AppComponent
override fun onCreate() {
super.onCreate()
mApiComponent = DaggerAppComponent.builder()
.appModule(AppModule(this))
.appClient(AppClient())
.build()
}
fun getAppComponent(): AppComponent {
return mApiComponent
}
}
And the component interface
#Singleton
#Component(modules = [AppModule::class, AppClient::class])
interface AppComponent {
fun inject(activity: LoginActivity)
}
Can someone help out as to what's wrong here?
It seems I was passing the wrong application class. So silly of me