Clean Architecture in Android using Dagger - android

I decided to add clean architecture to my project, I added three separate modules to the project: domain, data, presentation (app) and I divided the code into thees three modules. After that, I had a problem with Dagger, when I try to build the application, it says that it cannot access the WeatherDataApiService (this is the interface in which I make an API request using the retrofit library) I transferred this interface to the data module. In general, the problem is that I do not understand how to properly organize dependency injection so that classes have access to each other. The problem is that when Dagger is being built, access to the data module is closed. The problem is that I need to correctly distribute the dependencies between the modules. At the moment, my dependencies between the modules are built in this way - presentation(app) depends on the domain module, and the domain module depends on the data module
This is the interface WeatherDataApiService
interface WeatherDataApiService {
#GET("/v2.0/forecast/daily")
fun getWeatherData(
#Query("city") city: String,
#Query("days") days: Int,
#Query("units") degreeType: String
):Single<WeatherDataApiModel>
companion object {
operator fun invoke(): WeatherDataApiService {
val key = "40a7956799be42f49bc8b6ac4bb8e432"
val requestInterceptor = Interceptor{chain->
val url = chain.request()
.url() // HttpUrl
.newBuilder() // HttpUrl.Builder
.addQueryParameter("key", key) // HttpUrl.Builder
.build()
val request = chain.request()
.newBuilder() // Request.Builder
.url(url) // Request.Builder
.build() // Request
return#Interceptor chain.proceed(request) // Response
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(requestInterceptor) // OkHttpClient.Builder()
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://api.weatherbit.io")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
.create(WeatherDataApiService::class.java)
}
}
}
This is the repository located in the domain module.
class WeatherDataRepositoryImpl #Inject constructor(
private val weatherDataApiService : WeatherDataApiService,
private val mapper : WeatherDataMapper
) : WeatherDataRepository {
override fun getWeatherData(
city: String,
days: Int,
degreeType: String
): Single<WeatherData> =
weatherDataApiService.getWeatherData(city, days, degreeType)
.map { mapper.mapWeather(it) }
}
This is the use case code where I use the repository
class FetchWeatherDataUseCase #Inject constructor(private val weatherDataRepository: WeatherDataRepository) {
fun fetchWeatherData(city: String,days: Int,degreeType: String): Single<WeatherData> {
return weatherDataRepository.getWeatherData(city,days,degreeType)
}
}
This is the code of the Dagger module which is in domain
#Module
class WeatherDataRepositoryModule {
#Provides
#Singleton
fun providerWeatherDataRepository(
mapper: WeatherDataMapper,
weatherDataApiService: WeatherDataApiService
): WeatherDataRepository =
WeatherDataRepositoryImpl(
weatherDataApiService,
mapper
)
#Provides
#Singleton
fun providerApiService() = WeatherDataApiService()
#Provides
fun providerMapper() = WeatherDataMapper()
}
This is the code of the Dagger module which is in presentation(app)
#Module
class WeatherDataModule {
#Provides
#Singleton
fun provideFetchWeatherDataUseCase(weatherDataRepository: WeatherDataRepository) =
FetchWeatherDataUseCase(weatherDataRepository)
}
This is the code of the Dagger Component which is in presentation(app)
#Singleton
#Component(modules = [WeatherDataModule::class, WeatherDataRepositoryModule::class])
interface WeatherDataComponent {
fun injectWeatherDataFragment(weatherDataFragment: WeatherDataFragment)
}
Here I am creating a dagger, the code is in presentation (app)
class App : Application() {
lateinit var weatherDataComponent: WeatherDataComponent
override fun onCreate() {
super.onCreate()
weatherDataComponent = DaggerWeatherDataComponent.create()
}
}
Error text - cannot access WeatherDataApiService

Related

dagger2 is throwing an error: cannot be provided without an #Provides-annotated method. in my android project's build

Hello folks please help me to debug this dagger2 issue,I am very new to dagger2 and just started with a project.
public abstract interface ApplicationComponent {
^
java.util.Map<java.lang.Class<?
>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?>>> is injected at
dagger.android.DispatchingAndroidInjector(injectorFactoriesWithClassKeys, �)
dagger.android.DispatchingAndroidInjector<java.lang.Object> is injected at
com.example.dictionary.App.dispatchingServiceInjector
com.example.dictionary.App is injected at
com.example.dictionary.di.components.ApplicationComponent.inject(com.example.dictionary.App)
error: [Dagger/MissingBinding]
java.util.Map<java.lang.String,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?
>>> cannot be provided without an #Provides-annotated method.
My implementations are:
interface ApiService {
companion object {
const val BASE_URL= "https://od-api.oxforddictionaries.com/"
}
#GET("api/v2/entries/en-gb/")
fun getMeaning(#Query("word") word: String): Observable<Response>
}
}
open class RemoteDataSource #Inject constructor(private val apiService: ApiService) {
fun getWordMeaning(word: String) = apiService.getMeaning(word)
}
#ApplicationScope
#Component(modules = [ApplicationModule::class, NetworkModule::class])
interface ApplicationComponent {
fun inject(app: App)
fun inject(mainActivity: MainActivity)
}
#Module
class ApplicationModule(private val application: Application) {
#ApplicationScope
#Provides
fun provideApplicationContext(): Context = application.applicationContext
#ApplicationScope
#Provides
fun provideSharedPreferences(): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(application)
#ApplicationScope
#Provides
fun providerDisplayMetrics(): DisplayMetrics = application.resources.displayMetrics
}
#Module
class NetworkModule(
private val baseUrl: String,
private val application: Application
) {
companion object {
const val CACHE_SIZE = 10 * 1024 * 1024L // 10 MiB
const val TIME_OUT = 10L // time in minutes to get the response from server
}
#ApplicationScope
#Provides
fun provideGson(): Gson = GsonBuilder().create()
#ApplicationScope
#Provides
fun provideOkHttpCache() = Cache(application.cacheDir, CACHE_SIZE)
#ApplicationScope
#Provides
fun provideOkHttpClient(cache: Cache): OkHttpClient = with(OkHttpClient.Builder()) {
writeTimeout(3, TimeUnit.MINUTES)
.connectTimeout(3, TimeUnit.MINUTES)
.readTimeout(TIME_OUT, TimeUnit.MINUTES)
cache(cache)
addInterceptor(headersInterceptor())
build()
}
#ApplicationScope
#Provides
fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
#ApplicationScope
#Provides
fun provideApiService(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
private var authRequest: String? = null
private fun headersInterceptor() = Interceptor { chain ->
val requestBuilder = chain.request().newBuilder()
requestBuilder.addHeader("Accept", "application/json")
requestBuilder.addHeader("app_id", "SOME APP ID")
requestBuilder.addHeader("app_key", "Some API KEY")
chain.proceed(
requestBuilder.build()
)
}
}
class App : Application(), HasAndroidInjector {
#Inject
lateinit var dispatchingServiceInjector:
DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
applicationComponent.inject(this)
RxJavaPlugins.setErrorHandler {
it.printStackTrace()
}
}
override fun androidInjector(): AndroidInjector<Any> {
return dispatchingServiceInjector
}
val applicationComponent: ApplicationComponent by
lazy<ApplicationComponent>(mode = LazyThreadSafetyMode.NONE) {
DaggerApplicationComponent
.builder()
.applicationModule(ApplicationModule(this))
.networkModule(NetworkModule(ApiService.BASE_URL, this))
.build()
}
}
I tried re-building the code, also attempted invalidate cache and restart to re-install dependencies, as dagger2 requires re-building the project every time once a new dependency injection is implemented, followed YouTube tutorials, to implement this so far, but unable to detect the problem, although in the build crash its indicating to provide #Provides, but in my NetworkModule, ApplicationModule I set this #Provides annotation.
Please help to figure out the solution.
As suggested in this answer, just add your App class in the android manifest under the application tag.
<application ...
android:name = ".App" ...>
...

DaggerHilt Multi Modular Project Cannot access class issue

I have created a Multi-Modular project in android with dagger implementation. my sub module structure for data module is below.
When ever I try to access method of my NewsApiInterface from NewsDetailsImpl inside testRepo() method and on compile time I get the error
Cannot access class 'retrofit2.Response'. Check your module classpath
for missing or conflicting dependencies
Module:
data (Module)
repository (Implementation Module)
network(API Service Module)
cache (Database Module)
repository Module -> datasource
class NewsDetailsImpl #Inject constructor(
val db: NewsDatabase,
val service: NewsApiInterface
) : NewsDetailsRepository {
override suspend fun fetchNewsDetail(): Flow<News> = flow{
}
override suspend fun fetchNewsList(
query: String,
limitStart: String,
limitEnd: String
): Flow<List<News>> = flow {
}
fun testRepo(query: String){
val news = service.getNews(query)
}
}
repository module -> di
#Module
#InstallIn(SingletonComponent::class)
class RepositoryModule {
#Singleton
#Provides
fun provideNewsRepository( db: NewsDatabase,
service: NewsApiInterface
): NewsDetailsRepository {
return NewsDetailsImpl(db,service)
}
}
data module -> network module -> di
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Provides
#Singleton
fun providesNewsService() : NewsApiInterface {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(
OkHttpClient.Builder()
.addInterceptor { chain ->
val url = chain
.request()
.url
.newBuilder()
.addQueryParameter("apiKey", NetworkConstants.API_KEY)
.build()
chain.proceed(chain.request().newBuilder().url(url).build())
}
.build()
)
.baseUrl(BASE_URL)
.build()
return retrofit.create(NewsApiInterface::class.java)
}
}
data module -> network module
interface NewsApiInterface {
#GET("everything")
public fun getNews(#Query("q") query: String): Response<NewsDto>
}

Retrofit APIService Injection by Dagger Hilt in MVP Architecture's Model Class

I am trying to inject retrofit APIServices dependency into the model class. Here is My API Module Source Code:
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Singleton
#Provides
fun providesHttpLoggingInterceptor() = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
#Singleton
#Provides
fun providesOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()
#Singleton
#Provides
fun providesRetrofit(okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(ApiConfig.BASE_URL)
.client(okHttpClient)
.build()
#Singleton
#Provides
#Named("ApiService")
fun providesApiService(retrofit: Retrofit):ApiServices =
retrofit.create(ApiServices::class.java)
}
For User Registration, I am using MVP Architecture Pattern where FragmentRegistration.kt is view layer, RegistrationModel is model layer class
When I inject ApiServices dependency into FragmentRegistration, it works fine. But when I try to inject it into model layer class, which is RegistrationModel, It doesn't work.
RegistrationModel:
class RegistrationModel(
val presenter: RegistrationContract.Presenter
) : RegistrationContract.Model {
#Inject
#Named("ApiService")
lateinit var apiServices: ApiServices
override fun onDataReady(registrationData: RegistrationData) {
val map = mapOf(
"Accept" to "application/json",
"Content-Type" to "application/json"
)
apiServices.userRegistration(map, registrationData)
.enqueue(object : Callback<RegistrationResponse> {
override fun onResponse(
call: Call<RegistrationResponse>,
response: Response<RegistrationResponse>
) {
if (response.isSuccessful) {
Log.d(TAG, "onDataReady: ${response.body().toString()}")
} else {
val apiFailure = APIFailure(
response.code(),
response.message()
)
presenter.onSignupFailure(apiFailure)
Log.d(TAG, "onDataReady: Error ${response.code()}")
Log.d(TAG, "onDataReady: Error Body ${response.errorBody()}")
}
}
override fun onFailure(call: Call<RegistrationResponse>, t: Throwable) {
presenter.onSignupFailure(
APIFailure(-1, t.toString())
)
Log.d(TAG, "onFailure: $t")
}
})
}
companion object {
const val TAG = "RegistrationModel"
}
}
In the above's Code,
#Inject
#Named("ApiService")
lateinit var apiServices: ApiServices
this dependency injection is not working.
You are trying to inject a filed provided by Hilt into a class which is not managed by Hilt. This will not work out of the box. You have to define EntryPoint for you custom class, so the Hilt can perform injection. You can read how to do that here: https://developer.android.com/training/dependency-injection/hilt-android#not-supported

Hilt - Should I #Provides my Base URL or API Key?

I am new to Hilt and have negligible experience in Dagger 2 as well.
I am now doing a simple sample project for my self-learning, and trying to apply Hilt.
I use Retrofit to call APIs.
So, I am trying to refactor my code into using Hilt:
#Module
#InstallIn(SingletonComponent::class)
object WeatherAPIModule {
private const val BASE_URL = "https://api.openweathermap.org/data/2.5/"
#Provides
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
}
#Provides
fun provideOkHttpClient(loggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
}
#Provides
fun provideGsonConverterFactory(): GsonConverterFactory {
return GsonConverterFactory.create()
}
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient, gsonConverterFactory: GsonConverterFactory): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(gsonConverterFactory)
.build()
}
#Provides
fun provideWeatherService(retrofit: Retrofit): OpenWeatherService {
return retrofit.create(OpenWeatherService::class.java)
}
val weatherApiKey by lazy {
ApiKeyStore.getWeatherApiKey()
}
}
My question is, Should I use #Provides on Base URL and API Key as well?
Why do I ask
According to my understanding, if I have a class like this:
class SomeClass #Inject constructor() {
#Inject lateinit var someString: String
...
}
Hilt will try to find modules that provides String, and inject that String here. But clearly that's not what I want.
What should be the best way to do this?
Especially for weatherApiKey. I think I should not have leave it as an ordinary function, because that would lose the point of using Hilt at all.

SharedPreferences instance inside NetworkModule

I am facing with SharedPreferences problem. I would like to know how I can call SharedPreferences inside Retrofit. I mean, I have this following file :
#Module
class NetworkModule {
#Provides
internal fun provideGson(): Gson {
return GsonBuilder().create()
}
#Provides
internal fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
#Provides
internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
}
}
And in my Presenter, I have this following code:
override fun getSavedToken() {
mToken = mSharedPreferences.getString(TOKEN, TOKEN_UNAVAILABLE)
}
...
inner class GetAccessTokenSubscriber : ResourceObserver<AccessTokenBean>() {
override fun onNext(#NonNull accessToken: AccessTokenBean) {
mSharedPreferences.edit().putString(TOKEN, accessToken.token).apply()
getInformation()
}
override fun onError(#NonNull e: Throwable) {
mView?.displayError()
}
override fun onComplete() {
// Nothing to do
}
}
Currently, to set the token I put the Bearer $token in my repository / service
// Repository
val newToken = "Bearer $token"
return mService.getInfos(newToken)
// Service
fun getInfos(#Header("Authorization") token: String
I would like to know how I can put the Bearer + token inside my NetworkModule file?
Thank you for your time.
If you want to place the value on the interceptor, just call your SharedPreferences instance on the interceptor provider:
#Provides
internal fun provideOkHttpClient(sharedPrefs: SharedPrefs): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
Now dagger will look for that, but it won't find it, giving you an error. In that case, if you network module is also a singleton too just add a includes = [PreferencesModule::class], if not, you may need to set the current component dependent on the Singleton where you preferences module is located.

Categories

Resources