when I try to inject this line in my repositoryImpl in domain package:
private val userUseCase: UserUseCase by inject()
I got this error:
java.lang.StackOverflowError: stack size 1040KB
my package structure is: - data -> for retrofit, room, and ... - domain -> repository implement here and make connection between data and presentation - presentation -> for UI
I think with because of bad implement in DI and Provider of Koin. and this is my network-provider and network-module:
NetworkProvider:
/**
* provide HttpLoggingInterceptor for dependency injection with *Koin*
*
* @return the HttpLoggingInterceptor object <HttpLoggingInterceptor>
*
* @see HttpLoggingInterceptor
*/
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
return logger
}
/**
* provide OkHttpClient for dependency injection with *Koin*
*
* @param loggingInterceptor: a HttpLoggingInterceptor object, injected
* @param cache: a Cache object, injected
* @param hostnameVerifier: a HostnameVerifier object, injected
*
* @return the OkHttpClient built object <OkHttpClient>
*
* @see HttpLoggingInterceptor
* @see Cache
* @see HostnameVerifier
*/
fun provideOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
cache: Cache,
hostnameVerifier: HostnameVerifier
): OkHttpClient {
val clientBuilder = OkHttpClient()
.newBuilder()
.followRedirects(true)
.followSslRedirects(true)
.retryOnConnectionFailure(true)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.hostnameVerifier(hostnameVerifier)
.addInterceptor(loggingInterceptor)
.cache(cache)
return clientBuilder.build()
}
/**
* provide provideOkHttpClientForRefresh for dependency injection with *Koin*
*
* @param loggingInterceptor: a HttpLoggingInterceptor object, injected
* @param cache: a Cache object, injected
* @param hostnameVerifier: a HostnameVerifier object, injected
*
* @return the OkHttpClient built object <OkHttpClient>
*
* @see HttpLoggingInterceptor
* @see Cache
* @see HostnameVerifier
*/
fun provideOkHttpClientForRefresh(
loggingInterceptor: HttpLoggingInterceptor,
refreshTokenInterceptor: RefreshTokenInterceptor,
cache: Cache,
hostnameVerifier: HostnameVerifier
): OkHttpClient {
val clientBuilder = OkHttpClient()
.newBuilder()
.followRedirects(true)
.followSslRedirects(true)
.retryOnConnectionFailure(true)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.hostnameVerifier(hostnameVerifier)
.addInterceptor(loggingInterceptor)
.addInterceptor(refreshTokenInterceptor)
.cache(cache)
return clientBuilder.build()
}
/**
* provide OkHttpClient for dependency injection with *Koin*
*
* @param loggingInterceptor: a HttpLoggingInterceptor object, injected
* @param accessTokenInterceptor: a AccessTokenInterceptor object, injected
* @param accessTokenAuthenticator: a AccessTokenAuthenticator object, injected
* @param cache: a Cache object, injected
* @param hostnameVerifier: a HostnameVerifier object, injected
*
* @return the OkHttpClient built object <OkHttpClient>
*
* @see HttpLoggingInterceptor
* @see AccessTokenInterceptor
* @see AccessTokenAuthenticator
* @see Cache
* @see HostnameVerifier
*/
fun provideOkHttpClientForAuth(
loggingInterceptor: HttpLoggingInterceptor,
accessTokenInterceptor: AccessTokenInterceptor,
accessTokenAuthenticator: AccessTokenAuthenticator,
cache: Cache,
hostnameVerifier: HostnameVerifier
): OkHttpClient {
val clientBuilder = OkHttpClient()
.newBuilder()
.followRedirects(true)
.followSslRedirects(true)
.retryOnConnectionFailure(true)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.hostnameVerifier(hostnameVerifier)
.authenticator(accessTokenAuthenticator)
.addInterceptor(accessTokenInterceptor)
.addInterceptor(loggingInterceptor)
.cache(cache)
return clientBuilder.build()
}
/**
* provide Cache for dependency injection with *Koin*
*
* @param cacheDirection: a CacheDirection object, injected
*
* @return the Cache object <Cache>
*
* @see CacheDirection
* @see Cache
*/
fun provideCache(cacheDirection: CacheDirection): Cache {
val cacheFile = createDefaultCacheDir(cacheDirection.path, "api_cache")
return Cache(cacheFile, calculateDiskCacheSize(cacheFile))
}
/**
* provide HostnameVerifier for dependency injection with *Koin*
*
* @return the HostnameVerifier object <HostnameVerifier>
*
* @see HostnameVerifier
*/
fun provideHostnameVerifier(): HostnameVerifier {
return HostnameVerifier { hostname, _ ->
return@HostnameVerifier BuildConfig.API_URL_V1.contains(hostname)
}
}
/**
* provide Retrofit for dependency injection with *Koin*
*
* @param okHttpClient: a OkHttpClient object, injected
*
* @return the Retrofit built object <Retrofit>
*
* @see OkHttpClient
* @see Retrofit
*/
fun provideRetrofitForAuth(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder().baseUrl(BuildConfig.API_URL_V1).client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()).build()
}
/**
* provide Retrofit for dependency injection with *Koin*
*
* @param okHttpClient: a OkHttpClient object, injected
*
* @return the Retrofit built object <Retrofit>
*
* @see OkHttpClient
* @see Retrofit
*/
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder().baseUrl(BuildConfig.API_URL_V1).client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()).build()
}
/**
* provide Retrofit for dependency injection with *Koin*
*
* @param okHttpClient: a OkHttpClient object, injected
*
* @return the Retrofit built object <Retrofit>
*
* @see OkHttpClient
* @see Retrofit
*/
fun provideOkHttpClientForRefresh(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder().baseUrl(BuildConfig.API_URL_V1).client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()).build()
}
/**
* provide AuthApi for dependency injection with *Koin*
*
* @param retrofit: a Retrofit object, injected
*
* @return object created retrofit from retrofit's interface <AuthApi>
*
* @see Retrofit
* @see AuthApi
*/
fun provideAuthApi(retrofit: Retrofit): AuthApi = retrofit.create(AuthApi::class.java)
/**
* provide CheckTokenApi for dependency injection with *Koin*
*
* @param retrofit: a Retrofit object, injected
*
* @return object created retrofit from retrofit's interface <AuthApi>
*
* @see Retrofit
* @see CheckTokenApi
*/
fun provideCheckTokenApi(retrofit: Retrofit): CheckTokenApi =
retrofit.create(CheckTokenApi::class.java)
/**
* provide AuthApi for dependency injection with *Koin*
*
* @param retrofit: a Retrofit object, injected
*
* @return object created retrofit from retrofit's interface <AuthApi>
*
* @see Retrofit
* @see AuthApi
*/
fun provideUserApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java)
NetworkModule:
/**
* Create network provider module for dependency injection with *Koin*
*
* @see provideRetrofit
* @see provideOkHttpClient
* @see provideLoggingInterceptor
* @see provideAuthApi
* @see ResponseHandler
* @see AccessTokenInterceptor
* @see AccessTokenAuthenticator
*/
val networkModule = module {
factory { provideLoggingInterceptor() }
factory { provideCache(get()) }
factory { provideHostnameVerifier() }
factory { ResponseHandler() }
factory { AccessTokenInterceptor(get()) }
factory { AccessTokenAuthenticator(get(), get()) }
factory { RefreshTokenInterceptor(get(), get()) }
factory(named("allRequestOkHttpClient")) {
provideOkHttpClient(
get(),
get(),
get()
)
}
factory(named("refreshTokenRequestOkHttpClient")) {
provideOkHttpClientForRefresh(
get(),
get(),
get(),
get()
)
}
factory(named("authRequestOkHttpClient")) {
provideOkHttpClientForAuth(
get(), get(), get(), get(), get()
)
}
single(named("allRequestRetrofit")) { provideRetrofit(get(named("allRequestOkHttpClient"))) }
single(named("refreshTokenRequestRetrofit")) { provideOkHttpClientForRefresh(get(named("refreshTokenRequestOkHttpClient"))) }
single(named("authRequestRetrofit")) { provideRetrofitForAuth(get(named("authRequestOkHttpClient"))) }
factory { provideAuthApi(get(named("allRequestRetrofit"))) }
factory { provideCheckTokenApi(get(named("refreshTokenRequestRetrofit"))) }
factory { provideUserApi(get(named("authRequestRetrofit"))) }
}
AccessTokenAuthenticator:
/**
* a Authenticator class for add *Authorization* header into *okhttp* request
* implement from Authenticator (okhttp3.Authenticator) and KoinComponent (org.koin.core.KoinComponent)
*/
class AccessTokenAuthenticator(
private val checkTokenApi: CheckTokenApi,
private val userPreferences: UserPreferences
) : Authenticator {
/**
* override function for handle add *Authorization* into *okhttp* request
*
* @param route
* @param response
*
* @return Request
*/
@Nullable
override fun authenticate(route: Route?, response: Response): Request? {
synchronized(this) {
val newAccessToken =
checkTokenApi.checkToken().execute().body()
return if (userPreferences.token != newAccessToken?.apiObjects?.user?.token) {
if (newAccessToken?.apiObjects?.user?.token?.isNotEmpty() == true) {
userPreferences.token = newAccessToken.apiObjects.user.token
}
newRequestWithAccessToken(
response.request(),
newAccessToken?.apiObjects?.user?.token ?: ""
)
} else {
newRequestWithAccessToken(response.request(), userPreferences.token)
}
}
}
/**
* create request with custom header(Device-Id, Device-Token)
*
* @param request request for add header <Request>
* @param accessToken for add Uer-Token header provided from server <String>
*
* @return a Request with custom header(auth)
*/
private fun newRequestWithAccessToken(
request: Request,
newToken: String
): Request {
val req = request.newBuilder()
if (userPreferences.isLogin()) {
req.header("auth", newToken)
}
return req.build()
}
AccessTokenInterceptor:
/**
* a Interceptor class for handel or add *Authorization* header into *okhttp* request after get response
* implement from Interceptor (okhttp3.Interceptor) and KoinComponent (org.koin.core.KoinComponent)
*/
class AccessTokenInterceptor(
private val userPreferences: UserPreferences
) : Interceptor {
/**
* override function for handle or add *Authorization* into *okhttp* request after get response
*
* @param chain intercept's chain <Interceptor.Chain>
*
* @return Response
*/
override fun intercept(chain: Interceptor.Chain): Response {
/* if (userPreferences.isLogin()) {
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
response = getToken(chain, userPreferences.token)
}*/
return when (userPreferences.isLogin()) {
true -> {
val request = newRequestWithAccessToken(chain.request(), userPreferences.token)
chain.proceed(request)
}
else -> {
val request = newRequestWithoutAccessToken(chain.request())
chain.proceed(request)
}
}
}
/**
* create synchronized Api call for get device token and add into header and add into devicePreferences
*
* @param chain intercept's chain <Interceptor.Chain>
*
* @return a Response with custom header(Device-Id, Device-Token)
*/
/* private fun getToken(chain: Interceptor.Chain, previousToken: String): Response {
synchronized(this) {
val refreshTokenWithPreviousToken = chain.request().newBuilder()
refreshTokenWithPreviousToken.header("auth", previousToken)
val newAccessToken =
userApi.checkToken().execute().body()
if (userPreferences.token != newAccessToken?.apiObjects?.user?.token) {
if (newAccessToken?.apiObjects?.user?.token?.isNotEmpty() == true) {
userPreferences.token = newAccessToken.apiObjects.user.token
}
}
return chain.proceed(
newRequestWithAccessToken(
refreshTokenWithPreviousToken.build(),
newAccessToken?.apiObjects?.user?.token ?: ""
)
)
}
}*/
/**
* create request with custom header(Device-Id, Device-Token)
*
* @param request request for add header <Request>
* @param accessToken for add Device-Token header provided from server <String>
*
* @return a Request with custom header(Device-Id, Device-Token)
*/
private fun newRequestWithAccessToken(
request: Request,
accessToken: String
): Request {
val req = request.newBuilder()
if (userPreferences.isLogin()) {
req.header("auth", accessToken)
}
return req.build()
}
private fun newRequestWithoutAccessToken(
request: Request
): Request {
val req = request.newBuilder()
return req.build()
}
}
RefreshTokenInterceptor:
/**
* a Interceptor class for handel or add *Authorization* header into *okhttp* request after get response
* implement from Interceptor (okhttp3.Interceptor) and KoinComponent (org.koin.core.KoinComponent)
*/
class RefreshTokenInterceptor(
private val checkTokenApi: CheckTokenApi,
private val userPreferences: UserPreferences
) : Interceptor {
/**
* override function for handle or add *Authorization* into *okhttp* request after get response
*
* @param chain intercept's chain <Interceptor.Chain>
*
* @return Response
*/
override fun intercept(chain: Interceptor.Chain): Response {
var response: Response
response = when (userPreferences.isLogin()) {
true -> {
val request = newRequestWithAccessToken(chain.request(), userPreferences.token)
chain.proceed(request)
}
else -> {
val request = newRequestWithoutAccessToken(chain.request())
chain.proceed(request)
}
}
if (userPreferences.isLogin()) {
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
response = getToken(chain, userPreferences.token)
}
}
return response
}
/**
* create synchronized Api call for get device token and add into header and add into devicePreferences
*
* @param chain intercept's chain <Interceptor.Chain>
*
* @return a Response with custom header(Device-Id, Device-Token)
*/
private fun getToken(chain: Interceptor.Chain, previousToken: String): Response {
synchronized(this) {
val refreshTokenWithPreviousToken = chain.request().newBuilder()
refreshTokenWithPreviousToken.header("auth", previousToken)
val newAccessToken =
checkTokenApi.checkToken().execute().body()
if (userPreferences.token != newAccessToken?.apiObjects?.user?.token) {
if (newAccessToken?.apiObjects?.user?.token?.isNotEmpty() == true) {
userPreferences.token = newAccessToken.apiObjects.user.token
}
}
return chain.proceed(
newRequestWithAccessToken(
refreshTokenWithPreviousToken.build(),
newAccessToken?.apiObjects?.user?.token ?: ""
)
)
}
}
/**
* create request with custom header(Device-Id, Device-Token)
*
* @param request request for add header <Request>
* @param accessToken for add Device-Token header provided from server <String>
*
* @return a Request with custom header(Device-Id, Device-Token)
*/
private fun newRequestWithAccessToken(
request: Request,
accessToken: String
): Request {
val req = request.newBuilder()
if (userPreferences.isLogin()) {
req.header("auth", accessToken)
}
return req.build()
}
private fun newRequestWithoutAccessToken(
request: Request
): Request {
val req = request.newBuilder()
return req.build()
}
}
I have no idea why this problem occur but this end-point I try to call need auth header and token. but for another end-point without any like login and ... everything for correctly. may please guide my to a found a problem. thank you
I changed this class and everything now work correctly:
AccessTokenAuthenticator:
/**
* a Authenticator class for add *Authorization* header into *okhttp* request
* implement from Authenticator (okhttp3.Authenticator) and KoinComponent (org.koin.core.KoinComponent)
*/
class AccessTokenAuthenticator(
private val checkTokenApi: CheckTokenApi,
private val userPreferences: UserPreferences
) : Authenticator {
/**
* override function for handle add *Authorization* into *okhttp* request
*
* @param route
* @param response
*
* @return Request
*/
@Nullable
override fun authenticate(route: Route?, response: Response): Request? {
if (response.code() == 401) {
try {
val sendCall = checkTokenApi.refreshToken()
val refreshResult = sendCall.execute()
if (refreshResult.isSuccessful) {
//save Token
userPreferences.token = refreshResult.body()?.apiObjects?.user?.token ?: ""
//Replace Token
return response.request().newBuilder()
.header(
"auth",
refreshResult.body()?.apiObjects?.user?.token ?: ""
)
.build()
}
} catch (ex: Exception) {
// todo :: handle error
println(ex)
}
}
return null
}
}
AccessTokenInterceptor:
/**
* a Interceptor class for handel or add *Authorization* header into *okhttp* request after get response
* implement from Interceptor (okhttp3.Interceptor) and KoinComponent (org.koin.core.KoinComponent)
*/
class AccessTokenInterceptor(
private val userPreferences: UserPreferences
) : Interceptor {
/**
* override function for handle or add *Authorization* into *okhttp* request after get response
*
* @param chain intercept's chain <Interceptor.Chain>
*
* @return Response
*/
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newRequest: Request
newRequest = request.newBuilder()
.addHeader("auth", userPreferences.token)
.build()
return chain.proceed(newRequest)
}
}
RefreshTokenInterceptor:
/**
* a Interceptor class for handel or add *Authorization* header into *okhttp* request after get response
* implement from Interceptor (okhttp3.Interceptor) and KoinComponent (org.koin.core.KoinComponent)
*/
class RefreshTokenInterceptor(
private val userPreferences: UserPreferences
) : Interceptor {
/**
* override function for handle or add *Authorization* into *okhttp* request after get response
*
* @param chain intercept's chain <Interceptor.Chain>
*
* @return Response
*/
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newRequest: Request
newRequest = request.newBuilder()
.addHeader("auth", userPreferences.token)
.build()
return chain.proceed(newRequest)
}
}
and I think we can just remove RefreshTokenInterceptor and just use AccessTokenInterceptor. I hope this helps someone else.