Unit Testing : Retrofit 2 Exception - API interfaces must not extend other interfaces - android

I am not extending any interface and all my interfaces are independent. While performing unit testing I am getting the following exception. During normal API calls, everything works fine.
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(url)
.client(httpBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
return retrofit.create(MyApi::class.java)
The exception is thrown at retrofit.create(..) only while performing the unit test. Is there a way to avoid this error?

This can be fixed by using dependency injection for getting Service inside the method to be tested instead of creating the instance within the method.

Related

Why do we need to create a network service class as singleton in Android?

I am researching how to create a network service class using retrofit for making api calls, then I found almost all the examples I found were suggesting me to create a network service class as singleton as follows:
object ServiceGenerator{
val builder = Retrofit.Builder()
.baseUrl("url")
.addConverterFactory(
GsonConverterFactory.create());
val retrofit = builder.build();
val httpClient = OkHttpClient.Builder();
fun <T> createService(serviceClass:<T>):T {
return retrofit.create(serviceClass);
}
}
Any reason to create a network singleton class as singleton? what would happen if we create a new instance every time we make an api call ?
If any one can help me under this ?
The reason is that Retrofit.create() uses a reflective Proxy.newProxyInstance call under the hood (source) to create and instantiate a proxy class implementing the service interface, and reflection has historically been slow on Android devices. The instance can be safely cached, so recreating it on every API call will just result in slower networking.

How can i handle retrofit errors or get url request for to detect problems

I am trying to learn develop android applications and I'm trying to implement the latest approaches. So i use as many jetpack libraries as possible. (Dagger-Hilt, Coroutines, Retrofit etc)
Here is my question:
i have AppModule object for dependency injection.
Here is my retrofit object:
#Singleton
#Provides
fun provideConverterApi(): ConverterAPI {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ConverterAPI::class.java)
}
How can i get error messages from there or for example i need to see the url i use for the request, how can i do that?
You're doing great, to add a logger for your network call use this way:
.addConverterFactory(GsonConverterFactory.create())
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (DEBUG) BODY else NONE
})
.build()
Base on #Amjad Alwareh, remember to add the dependence of the HTTP logging interceptor.
implementation "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}", // 3.12.1 or other version
Maybe the DEBUG should be BuildConfig.DEBUG

How Observable is created in retrofit without calling it from scheduler?

I am working on rx-java and retrofit and I have some queries related to it.I am not able to understand that when we call below code then how Observable(Observable) is created ?
According to my understanding it should be called only during the time of scheduling and subscribing.
CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class);
Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc");
There is a call adapter method in Retrofit Builder which convert Call responses to corresponding one in RxJava
Retrofit.Builder()
.client(okHttpClient)
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()
.build()

How to have multiple base URL in retrofit2 using Kotlin and Kodein structure

I have followed THIS tutorial for MVVM and Retrofit2 with Kodein structure/framework. I wanted to know, Using this same framework/structure how can i have multiple base URLs in a single App.
Below is the code of "MyApi" interface which have an interceptor class as parameter.
companion object {
operator fun invoke(
networkConnectionInterceptor: NetworkConnectionInterceptor
): MyApi {
val okkHttpclient = OkHttpClient.Builder()
.addInterceptor(networkConnectionInterceptor)
.readTimeout(20, TimeUnit.SECONDS)
.build()
return Retrofit.Builder()
.client(okkHttpclient)
.baseUrl("http://my-base-url")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MyApi::class.java)
}
}
And here is how Iam initializing MyApi interface in Application class:
bind() from singleton { PreferenceProvider(instance()) }
bind() from singleton { NetworkConnectionInterceptor(instance(), instance()) }
bind() from singleton { MyApi(instance()) }
Here instance() in MyApi is obviously NetworkConnectionInterceptor.
I have seen a lot of examples on stackoverflow and medium but i didnt got any help.
I think i found a work around to achive this. There are two solutions...
First Solution:
You can create a new interface for other microservice (base url) and use it just like the first one. Now there are some pros and cons for this.
Pros:
Both the interfaces will be independent of each other.
You can use either of the interface or both in same activity as you need.
Cons:
If there is another microservice poped up , you have to create one more interface for that. 😿
If you even have to 2 microservice and you have to run same application on development and qa server providing an option for testers and developers to switch between qa and dev servers at run time the you need to have 4 interfaces and 2 extra for production that means 6 interfaces, It will become very had to manage it.
Second Solution:
You can use #URL annotation provided by retrofit2. Now if you do this there will be no base_url, you have to pass the URL and microservice name in a common function which will return you a full URL based on what server users/testers are on (dev/qa or prod).
Pros:
No extra interfaces, Only one will work out.
Easy management of all the API calls because of the common function.
Cons:
You have to call the common funtion in #URL annotation in each and every API call.
I dont see any more. 😊

Retrofit 2.6.0 exception: java.lang.IllegalArgumentException: Unable to create call adapter for kotlinx.coroutines.Deferred

I have a project with Kotlin coroutines and Retrofit.
I had these dependencies:
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
Today I have updated Retrofit to 2.6.0 in the project. In https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter it is written that it is deprecated now. In https://github.com/square/retrofit/blob/master/CHANGELOG.md#version-260-2019-06-05 it is written that Retrofit currently supports suspend.
So, I removed retrofit2-kotlin-coroutines-adapter:0.9.2 and in Retrofit client changed these lines:
retrofit = Retrofit.Builder()
.baseUrl(SERVER_URL)
.client(okHttpClient)
.addConverterFactory(MyGsonFactory.create(gson))
//.addCallAdapterFactory(CoroutineCallAdapterFactory()) - removed it.
.build()
When run, the first request catches an exception:
java.lang.IllegalArgumentException: Unable to create call adapter for kotlinx.coroutines.Deferred<com.package.model.response.UserInfoResponse>
for method Api.getUserInfo
As I understood, instead of CoroutineCallAdapterFactory() I could use CallAdapter.Factory(), but it is abstract.
If in Api class I change a request adding suspend in the beginning:
#FormUrlEncoded
#POST("user/info/")
suspend fun getUserInfo(#Field("token") token: String): Deferred<UserInfoResponse>
override suspend fun getUserInfo(token: String): Deferred<UserInfoResponse> =
service.getUserInfo(token)
I get this exception:
java.lang.RuntimeException: Unable to invoke no-args constructor for kotlinx.coroutines.Deferred<com.package.model.response.UserInfoResponse>. Registering an InstanceCreator with Gson for this type may fix this problem.
Reading https://github.com/square/retrofit/blob/master/CHANGELOG.md#version-260-2019-06-05 I saw:
New: Support suspend modifier on functions for Kotlin! This allows you
to express the asynchrony of HTTP requests in an idiomatic fashion for
the language.
#GET("users/{id}") suspend fun user(#Path("id") long id): User
Behind the scenes this behaves as if defined as fun user(...):
Call and then invoked with Call.enqueue. You can also return
Response for access to the response metadata.
Currently this integration only supports non-null response body types.
Follow issue 3075 for nullable type support.
I changed requests so: added suspend and removed Deferred:
#FormUrlEncoded
#POST("user/info/")
suspend fun getUserInfo(#Field("token") token: String): UserInfoResponse
override suspend fun getUserInfo(token: String): UserInfoResponse =
service.getUserInfo(token)
Then in interactor (or simply when called the method getUserInfo(token)) removed await():
override suspend fun getUserInfo(token: String): UserInfoResponse =
// api.getUserInfo(token).await() - was before.
api.getUserInfo(token)
UPDATE
Once I encountered a situation when downloading PDF files required removing suspend in Api class. See How to download PDF file with Retrofit and Kotlin coroutines?.
In my case I was missing the CoroutineCallAdapterFactory in my Retrofit initialization. Retrofit v2.5.0
Before:
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
After: (working code)
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()

Categories

Resources