I'm receiving a response from BE and the response is a base64 encoded image. The response looks like this:
{"image":"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/ ...} (whole response here: https://pastebin.com/ViFTAhRw)
Looks like a property named image followed by a string. So I've created my model class:
#JsonClass(generateAdapter = true)
data class ApiBase64Image(
#field:Json(name = "image") val imageString: String?
) {
fun toDomain(): Base64Image {
return Base64Image(imageString.orEmpty())
}
}
And finally, my DI object:
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Provides
#Singleton
fun provideApi(builder: Retrofit.Builder): MyApi {
return builder
.build()
.create(MyApi::class.java)
}
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.baseUrl(ApiConstants.BASE_ENDPOINT)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
}
#Provides
fun provideOkHttpClient(
authenticationInterceptor: AuthenticationInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authenticationInterceptor)
.build()
}
}
This code, however, does not work as I'm receiving errors:
Unable to create converter for class ... .ApiBase64Image
Failed to find the generated JsonAdapter class for class ... .ApiBase64Image
I'm not sure what is giving Moshi problems. Is it the data class serialization? Or my DI setup? Or something else entirely?
#JsonClass(generateAdapter = true)
This is used by Codegen, so you need to add that dependency :
plugins {
id("com.google.devtools.ksp").version("1.6.10-1.0.4") // Or latest version of KSP
}
dependencies {
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.13.0")
}
Related
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.
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
I am trying to have DI for creating a retrofit instance with KOIN
this is the module
val networkModule = module {
factory { provideRetrofit(get()) }
single { provideNetworkApi(get()) }
}
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(NetworkConstant.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(OkHttpClient.Builder().build())
.build()
}
fun provideNetworkApi(retrofit: Retrofit): NetworkCall =
retrofit.create(NetworkCall::class.java)
In the application class
class BaseApp :Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#BaseApp)
modules(listOf(prefModule,networkModule))
}
}
}
The compiler is showing error on provideRetrofit(get()
Too many arguments for public fun provideRetrofit():
I was able to create DI for another class
val prefModule = module {
single { AppPreference(androidContext()) }
}
Can someone point me whats wrong here?
Was being caused because of get()
val networkModule = module {
factory { provideRetrofit() }
single { provideNetworkApi(get()) }
}
Would it be acceptable skip separation of Retrofit instance (what's benefit?) and simply combine it in the goal of creating your NetworkCall?
Here is an example of what I mean:
val appModule = module {
single {
val httpInterceptor = HttpLoggingInterceptor()
httpInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
Retrofit.Builder()
.client(
OkHttpClient.Builder()
.addInterceptor(httpInterceptor).build()
)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://postman-echo.com/")
.build()
.create(PostmanEcho::class.java)
}
single {
PostmanEchoRepository(get(), get())
}
....
My PostmanEchoRepository is using equivalent of your NetworkCall as first param.
Details: https://github.com/oradkovsky/weird-data-input/blob/master/app/src/main/java/com/ror/weirddatainput/di/AppModule.kt
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.
I have a class that uses Dagger 2 for dependency injection. Now I want to switch to Koin for dependency injection. There are modules in Koin and I want to make a module out of the class or whatever can be done.
#Module
class NetModule(private val baseUrl: String) {
#Provides
#Singleton
fun providesOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient = OkHttpClient.Builder().addInterceptor(
httpLoggingInterceptor).build()
#Provides
#Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message -> Logger.d("NETWORK: $message") })
interceptor.level = HttpLoggingInterceptor.Level.NONE
return interceptor
}
#Provides
#Singleton
fun providesMoshi(): Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
#Provides
#Singleton
fun providesRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): Retrofit {
return Builder().client(okHttpClient).baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
#Provides
#Singleton
fun providesApiInterface(retrofit: Retrofit): ApiInterface = retrofit.create(
ApiInterface::class.java)
}
Koin uses a DSL for describing modules. Usually you'd declare the module itself on a top-level. Since you need to provide baseUrl, you'd have to create a factory for it.
The #Provides annotation is completely irrelevant, but #Singleton needs to be translated and does so with single. To retrieve the dependencies, just call get().
fun netModule(baseUrl: String) = module {
single {
HttpLoggingInterceptor(
HttpLoggingInterceptor.Logger { message ->
Logger.d("NETWORK: $message")
}).apply {
level = HttpLoggingInterceptor.Level.NONE
}
}
single {
OkHttpClient.Builder()
.addInterceptor(get<HttpLoggingInterceptor>())
.build()
}
single {
Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
}
single {
Retrofit.Builder()
.client(get())
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
single { get<Retrofit>().create(ApiInterface::class.java) }
}