I am using reflection for change retrofit base url dynamically but I can change once in activity, after that I can not change it. Why it is not working when I try to change multiple times.
Set dynamic base url using Retrofit 2.0 and Dagger 2
I do not know the domain I just know the end points, I get the domain adress from user.
Just use different modules and api for different URLs. And then use #Named annotation to determine the correct api.
Api module with HOST url
#Module
class ApiModule {
#Provides
#Singleton
#Named("Interceptor")
fun provideInterceptor(context: Context, sharedPrefsStorage: SharedPrefsStorage, #Named("ApiAuth") apiAuth: ApiAuth,realmProvider: DbProvider<Realm>): Interceptor {
return ApiInterceptor(context, sharedPrefsStorage, apiAuth,realmProvider)
}
#Provides
#Singleton
#Named("HttpClient")
fun provideOkHttpClient(#Named("Interceptor") interceptor: Interceptor): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
builder.readTimeout(30, TimeUnit.SECONDS)
builder.connectTimeout(30, TimeUnit.SECONDS)
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.addInterceptor(loggingInterceptor)
return builder.build()
}
#Provides
#Singleton
#Named("Retrofit")
fun provideRetrofitBuilder(#Named("HttpClient") okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl(BuildConfig.HOST)
}
#Provides
#Singleton
#Named("Api")
fun provideApi(#Named("Retrofit") builder: Retrofit.Builder): Api {
return builder.build().create<Api>(Api::class.java)
}
companion object {
val TAG = ApiModule::class.java.simpleName
}
}
ApiAuthModule with HOST_2 url
#Module
class ApiAuthModule {
#Provides
#Singleton
#Named("InterceptorAuth")
fun provideInterceptor(): Interceptor {
return ApiAuthInterceptor()
}
#Provides
#Singleton
#Named("HttpClientAuth")
fun provideOkHttpClient(#Named("InterceptorAuth") interceptor: Interceptor): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
builder.readTimeout(5, TimeUnit.SECONDS)
builder.connectTimeout(5, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.addInterceptor(loggingInterceptor)
}
return builder.build()
}
#Provides
#Singleton
#Named("RetrofitAuth")
fun provideRetrofitBuilder(#Named("HttpClientAuth") okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl(BuildConfig.HOST_2)
}
#Provides
#Singleton
#Named("ApiAuth")
fun provideApi(#Named("RetrofitAuth") builder: Retrofit.Builder): ApiAuth {
return builder.build().create<ApiAuth>(ApiAuth::class.java)
}
companion object {
val TAG = ApiModule::class.java.simpleName
}
}
Usage in AppModule:
#Provides
#Singleton
fun worksRepository(#Named("Api") api: Api, #Named("ApiAuth") api2: ApiAuth): IWorksRepository {
return WorksRepository(api, api2)
}
Related
I have a Hilt module to provide Retrofit API in my app like so:
#Module
#InstallIn(SingletonComponent::class)
object NetworkDataModule {
#Provides
#Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
.build()
}
#Provides
#Singleton
fun provideMyApi(client: OkHttpClient): MyApi {
return Retrofit.Builder()
.baseUrl(MyApi.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(client)
.build()
.create()
}
}
And it is my API interface:
interface MyApi {
#GET("search")
suspend fun search(): searchDto
companion object {
const val BASE_URL = "https://myapi.com/"
}
}
The question is how the Retrofits create() extension function knows which class should it return despite I did not pass any information about the MyApi interface to it. The create function implementation is this:
inline fun <reified T> Retrofit.create(): T = create(T::class.java)
How does create(T::class.java) return MyApi in spite of I did not provide any type for T?
My retrofit version is 2.9.0
Hilt network module is setup like below:
#Module
#InstallIn(SingletonComponent::class)
class NetworkModule {
private val TIMEOUT_UNIT = TimeUnit.MILLISECONDS
private val TIMEOUT_MILLIS: Long = 10000
#Provides
fun provideApi(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
#Singleton
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient, converter: Converter.Factory): Retrofit =
Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(converter)
.build()
#Provides
fun provideOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.retryOnConnectionFailure(true)
builder.connectTimeout(3 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.readTimeout(6 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.writeTimeout(12 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.addInterceptor(loggingInterceptor)
if (condition == true) {
builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("localhost", HTTP_PROXY_PORT)))
}
return builder.build()
}
#Provides
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
if (BuildConfig::DEBUG.get()) {
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
loggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
}
return loggingInterceptor
}
#Provides
fun provideConverterFactory(): Converter.Factory {
return GsonConverterFactory.create()
}
#Provides
fun providesGson(): Gson {
return Gson()
}
}
I want to customize the retrofit instance at runtime with or without the proxy according to some condition. How do I do that?
Koin had unloadModule option for reinitializing modules, does Hilt has any similar feature or behaviour?
I am new with dagger using. So, I can't solve whats the problem with this. I just want to ask here to solve it.
This is the error:
C:\Users\msi\Documents\MyAndroidProjects\MovieProjects\app\build\generated\hilt\component_sources\debug\com\example\movieapp\App_HiltComponents.java:128:
error: [Dagger/MissingBinding]
com.example.movieapp.api.MovieAppService cannot be provided without an
#Provides-annotated method. public abstract static class SingletonC
implements App_GeneratedInjector,
^
com.example.movieapp.api.MovieAppService is injected at
com.example.movieapp.repository.MovieRepository(movieAppService)
com.example.movieapp.repository.MovieRepository is injected at
com.example.movieapp.viewmodel.MainViewModel(repository, �)
com.example.movieapp.viewmodel.MainViewModel is injected at
com.example.movieapp.viewmodel.MainViewModel_HiltModules.BindsModule.binds(arg0)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>>
is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap()
[com.example.movieapp.App_HiltComponents.SingletonC ?
com.example.movieapp.App_HiltComponents.ActivityRetainedC ?
com.example.movieapp.App_HiltComponents.ViewModelC]
MainViewModel.kt
#HiltViewModel
class MainViewModel#Inject constructor(
private val repository: MovieRepository,
#ApplicationContext private val context: Context
) : ViewModel() {
val movieList = MutableLiveData<Resource<Movie>>()
fun getAllMovies(movieName: String) {
movieList.postValue(Resource.Loading())
viewModelScope.launch {
try {
if (hasInternetConnection(context)) {
val response = repository.getMovies(movieName, "ffe9063f")
movieList.postValue(Resource.Success(response.body()!!))
} else
movieList.postValue(Resource.Error("Internet yok"))
} catch (ex: Exception) {
when (ex) {
is IOException -> movieList.postValue(Resource.Error("Network Failure " + ex.localizedMessage))
else -> movieList.postValue(Resource.Error("Conversion Error"))
}
}
}
}
}
MovieRepository.kt
#Singleton
class MovieRepository #Inject constructor(private val movieAppService: MovieAppService) {
suspend fun getMovies(title: String, aKey: String): Response<Movie> = withContext(
Dispatchers.IO
) {
val movies = movieAppService.getMovies(title = title, aKey = aKey)
movies
}
}
ApiModule.kt
class ApiModule {
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Provides
#Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
#Provides
#Singleton
fun provideOkHttpClient(logging: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(logging)
.connectTimeout(15, TimeUnit.SECONDS) // connect timeout
.readTimeout(15, TimeUnit.SECONDS)
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(ENDPOINT)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
}
#Provides
#Singleton
fun provideMovieAppService(retrofit: Retrofit): MovieAppService {
return retrofit.create(MovieAppService::class.java)
}
}
}
MovieAppService.kt
interface MovieAppService {
companion object {
const val ENDPOINT = "http://www.omdbapi.com/"
}
#GET(".")
suspend fun getMovies(#Query("t") title: String,#Query("apikey") aKey: String): Response<Movie>
}
Do not wrap your singleton object module with a same named class. Change your module file like this or change your class name
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Provides
#Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
#Provides
#Singleton
fun provideOkHttpClient(logging: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(logging)
.connectTimeout(15, TimeUnit.SECONDS) // connect timeout
.readTimeout(15, TimeUnit.SECONDS)
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(ENDPOINT)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
}
#Provides
#Singleton
fun provideMovieAppService(retrofit: Retrofit): MovieAppService {
return retrofit.create(MovieAppService::class.java)
}
}
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) }
}
Dagger 2 is generating multiple instances of retrofit interceptor despite marking it as singleton in dagger module. Now the problem is that AuthorizationInterceptor constructor gets called twice which I don't understand why and because of that the headers that I set after getting result from login API get sets to a different instance of Interceptor and while making call to some other API which requires authorizationToken the token is unset.
Here is my ApiModule
#Module
open class ApiModule {
#Provides
#Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}
#Provides
#Singleton
fun provideHeaderInterceptor(): Interceptor {
return AuthorizationInterceptor()
}
#Provides
#Singleton
fun provideHttpClient(interceptor: HttpLoggingInterceptor, headerInterceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.addInterceptor(headerInterceptor)
.build()
}
#Provides
#Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, moshi: Moshi, apiConfig: ApiConfig): Retrofit {
return Retrofit.Builder()
.baseUrl(apiConfig.baseUrl)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
#Provides
#Singleton
fun provideFrappApi(retrofit: Retrofit): FrappApi {
return retrofit.create(FrappApi::class.java)
}
Here is my AuthorizationInterceptor class
#Singleton
class AuthorizationInterceptor #Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain?): Response {
val request = chain?.request()
val requestBuilder = request?.newBuilder()
if (request?.header("No-Authorization") == null && authorization.isNotEmpty()) {
requestBuilder?.addHeader("Authorization", authorization)
}
return chain?.proceed(requestBuilder!!.build())!!
}
private var authorization: String = ""
fun setSessionToken(sessionToken: String) {
this.authorization = sessionToken
}
}
You dont need to make a provide method if you do a constructor injection.
Remove the provideHeaderInterceptor method, then update the provideHttpClient method like below,
#Provides
#Singleton
fun provideHttpClient(interceptor: HttpLoggingInterceptor,
headerInterceptor: AuthorizationInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.addInterceptor(headerInterceptor)
.build()
}
Or if you dont like the solution above, you can remove the #Singleton and #Inject in your AuthorizationInterceptor class.