In API declaration I have this code:
#FormUrlEncoded
#POST("loginByPass/")
fun loginByPassword(#Field("login") login: String,
#Field("password") password: String,
#Field("phone") phone: String) : Observable<AuthResponse>
Retrofit object I create this way:
class API {
companion object {
fun getRetrofitAPI() : IAPI {
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addNetworkInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(SERVER_ADDRESS)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return retrofit.create(IAPI::class.java)
}
}
}
And request:
api.loginByPassword(login, password, "")
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({
Log.w("USER_DB", "CODE: " + it.code)
}, {
this.sayError(it.localizedMessage)
}).dispose()
And I have no response at all! In logs it seems like I haven't sent any request. Also I have seen sent packages - from emulator was sent nothing. I'm calling to server IP address by HTTP.
Could you give me piece of advice, what's the problem?
According to #DrawnRaccoon 's answer, .dispose() destroys the request, so I should use CompositeDisposable to collect all requests and dispose them in onDestroy() method.
Related
I'm using retrofit in my app and everything is ok except one thing. After disabling internet connection, enabling it back and making request to api (okhttp logger says that GET request was handled), response comes in a few minutes. And when i make next request, response comes immediately.
Also, for the slow response, okhttp logger says that it has been handled in ~50 milliseconds, but prints that log after a few minutes too.
In project i'm using koin, but i doubt it is the root of the problem. Anyway i defined retrofit, okhttpclient and interceptors as single.
And maybe it'll help, if i set read and write timeouts to, for example, 5 seconds in okhttp client i'll get timeout exception.
So what's happening and how to handle with that problem?
Retrofit:
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client) // my OkHttpClient instance
.build()
OkHttpClient:
OkHttpClient.Builder()
.addInterceptor(networkConnectionInterceptor)
.addInterceptor(loggingInterceptor)
.writeTimeout(5, TimeUnit.MINUTES)
.readTimeout(5, TimeUnit.MINUTES)
.build()
NetworkConnectionInterceptor and NoConnectivityException classes:
class NetworkConnectionInterceptor(private val context: Context) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
if (!isConnected) {
throw NoConnectivityException()
}
val builder: Request.Builder = chain.request().newBuilder()
return chain.proceed(builder.build())
}
val isConnected: Boolean
get() {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = connectivityManager.activeNetworkInfo
return netInfo != null && netInfo.isConnected
}
}
class NoConnectivityException : IOException() {
override val message: String
get() = "No Internet Connection"
}
I am using RxAndroid + Retrofit to make http request. Code looks like below:
Interceptor headerInterceptor = getHeaderInterceptor();
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.callTimeout(5, TimeUnit.SECONDS)
.addInterceptor(headerInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
sRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build();
Use it like this:
ApiProvider.provideApi(MyApi.class)
.submit(id, mRequest)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
response -> {
Log.w("tag", "success");
},
throwable -> {
Log.w("tag", "error");
}
);
I set connectTimeout / readTimeout / writeTimeout to be 60 seconds, and set callTimeout to be 5 seconds.
I know this configuration may be not reasonable but I just want to get a timeout exception after 5 seconds, and the Log.w("tag", "error"); could be called.
However, I found this line will never be called for my testing. And if I set connectionTimeout to 1 second, then this line will be called immediately.
So what I should do if I want callTimeout to trigger the log error line?
As for as I can see, I think your problem maybe isn't from how many seconds you set for callTimeout(5, TimeUnit.SECONDS), I think your maybe Rx stream have already throw some errors, so stream just break you can get any response from here. However, you reset time seconds to 1s, and then you restart app, this time stream not break and you get error.
So simple re-test it again to make sure your stream won't break even before enter this subscribe.
And I have been test with some stupid implementations:
class MainActivity : AppCompatActivity() {
val delayInterceptor = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
Thread.sleep(6000L)
return chain.proceed(chain.request())
}
}
val client: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.callTimeout(5, TimeUnit.SECONDS)
.addInterceptor(delayInterceptor)
.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
data class Wiki(
#SerializedName("type")
val type: String
)
interface WikiService {
#GET("api/rest_v1/page/random/summary")
fun getRandomSummary(): Single<Wiki>
}
#SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
retrofit.create(WikiService::class.java)
.getRandomSummary()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.d("tag", "success")
}, {
Log.e("tag", "error")
})
}
}
I finally find the the cause.
I was using com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory, which had been deprecated per its readme: https://github.com/JakeWharton/retrofit2-rxjava2-adapter
This is now DEPRECATED!
Retrofit 2.2 and newer have a first-party call adapter for RxJava 2: https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2
After switching to retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory, everything start working nicely.
And BTW for any guy who might be interested in what the differences are between them two? Attach the key info I found below:
In my application i want get data from server and for this i should add some header such as Accept and Content_Type .
For connect to server i used Retrofit library.
For set headers i use okHttp client and i write below codes, but not set header to api response!
My Client codes:
class ApiClient() {
private val apiServices: ApiServices
init {
//Gson
val gson = GsonBuilder()
.setLenient()
.create()
//Http log
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level =
if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
//Http Builder
val clientBuilder = OkHttpClient.Builder()
clientBuilder.interceptors().add(loggingInterceptor)
clientBuilder.addInterceptor { chain ->
val request = chain.request()
request.newBuilder().addHeader(
CONTENT_TYPE,
APPLICATION_JSON
).build()
chain.proceed(request)
}
clientBuilder.addInterceptor { chain ->
val request = chain.request()
request.newBuilder().addHeader(
ACCEPT,
APPLICATION_JSON
).build()
chain.proceed(request)
}
//Http client
val client = clientBuilder
.readTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.callTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
//Retrofit
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL + BASE_URP_PREFIX)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.build()
//Init mapApiServices
apiServices = retrofit.create(ApiServices::class.java)
}
companion object {
private var apiClient: ApiClient? = null
fun getInstance(): ApiClient =
apiClient ?: synchronized(this) {
apiClient
?: ApiClient().also {
apiClient = it
}
}
}
}
How can i fix it?
The first option to add a static header is to define the header and respective value for your API method as an annotation. The header gets automatically added by Retrofit for every request using this method. The annotation can be either key-value-pair as one string or as a list of strings.
The example above shows the key-value-definition for the static header:
Further, you can pass multiple key-value-strings as a list encapsulated in curly brackets {} to the #Headers annotation.
How you can pass multiple key-value-strings as a list encapsulated in curly brackets:
A more customizable approach are dynamic headers. A dynamic header is passed like a parameter to the method. The provided parameter value gets mapped by Retrofit before executing the request.
Define dynamic headers where you might pass different values for each request:
Happy Coding!! 😎
I am trying to access IBM's Speech to Text service without using the library. I am using Retrofit with GSON.
The issue is in the authentication, which apparently does not occur correctly, returning code 401. From the official documentation, the HTTP request should come in this format
curl -X POST -u "apikey:{apikey}" \
--header "Content-Type: audio/flac" \
--data-binary #{path_to_file}audio-file.flac \
"{url}/v1/recognize"
When I test the curl command with my credentials, the service works fine.
This is the interface I'm using
interface SpeechToTextApi {
#Multipart
#POST("v1/recognize")
fun speechToText(
#Header("Authorization") authKey: String,
#Part("file") filename: RequestBody,
#Part voiceFile: MultipartBody.Part
): Call<List<SpeechToText>>
}
where I have the following data classes
data class SpeechToText(val results: List<SttResult>)
data class SttResult(val alternatives: List<RecognitionResult>, val final: Boolean)
data class RecognitionResult(val confidence: Float, val transcript: String)
and this is how I set up Retrofit
private val retrofit = Retrofit.Builder()
.baseUrl(STT_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
private val service = retrofit.create(SpeechToTextApi::class.java)
while calling the actual service looks like this
val requestFile = RequestBody.create(MediaType.parse("audio/mp3"), file.name)
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
service
.speechToText(getString(R.string.stt_iam_api_key), requestFile, body)
.enqueue(object: Callback<List<SpeechToText>> {
override fun onResponse(call: Call<List<SpeechToText>>, response: Response<List<SpeechToText>>) {
val listOfStts = response.body()
Log.d(TAG, "Response code: ${response.code()}")
if (listOfStts != null) {
for (stt in listOfStts) {
for (res in stt.results) {
Log.d(TAG, "Final value: ${res.final}")
for (alt in res.alternatives) {
Log.d(TAG, "Alternative confidence: ${alt.confidence}\nTranscript: ${alt.transcript}")
Toast.makeText(this#MainActivity, alt.transcript, Toast.LENGTH_SHORT).show()
}
}
}
}
}
override fun onFailure(call: Call<List<SpeechToText>>, t: Throwable) {
Log.d(TAG, "Error: ${t.message}")
t.printStackTrace()
}
})
Recordings are MP3 files, for which I am sure they are stored correctly and accessible. I have replaced audio/flac with audio/mp3 as well.
Issue seems to be in the way authentication works. Prior to the code I have shown above, I've used
private val retrofit = Retrofit.Builder()
.baseUrl(STT_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request()
val headers = request
.headers()
.newBuilder()
.add("Authorization", getString(R.string.stt_iam_api_key))
.build()
val finalRequest = request.newBuilder().headers(headers).build()
chain.proceed(finalRequest)
}
.build())
.build()
but the same response code 401 persisted. Of course, the interface method lacked the #Header parameter.
Any sort of help is much appreciated.
I am kind of saddened by the fact nobody was able to solve this one sooner, but here's the solution I came across by accident when working on a different project altogether.
As you can see from the curl command, authentication comes in the form of username: password pattern, in this case, username being apikey string and password is your API key.
So the way you should tackle this is by building your Retrofit instance this way:
fun init(token: String) {
//Set logging interceptor to BODY and redact Authorization header
interceptor.level = HttpLoggingInterceptor.Level.BODY
interceptor.redactHeader("Authorization")
//Build OkHttp client with logging and token interceptors
val okhttp = OkHttpClient().newBuilder()
.addInterceptor(interceptor)
.addInterceptor(TokenInterceptor(token))
.build()
//Set field naming policy for Gson
val gsonBuilder = GsonBuilder()
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
//Build Retrofit instance
retrofit = Retrofit.Builder()
.baseUrl(IBM_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
.client(okhttp)
.build()
}
and create this custom interceptor
class TokenInterceptor constructor(private val token: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val requestBuilder = original
.newBuilder()
.addHeader("Authorization", Credentials.basic("apikey", token))
.url(original.url)
return chain.proceed(requestBuilder.build())
}
}
You need to use Credentials.basic() in order to encode credentials.
I really hope somebody with a similar issue stumbles across this and saves themselves some time.
I have an issue with Retrofit and RxJava2 in Kotlin.
Here is my build.gradle:
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.1' implementation 'com.squareup.retrofit2:converter-moshi:2.6.1'
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.8.0'
// RxJava
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.11'
I have the following Retrofit interface with a single request, login. I also have a simple data class called User.
data class User (
private val firstName: String
)
interface ApiService {
#POST("auth/login")
fun login(#Body body: String) : Observable<User>
}
When I try to make a request and subscribe to it, no request is being sent. I have checked my server logs and the server is not receiving anything at all. There are also no errors in the app logs. I'm not sure what I am doing wrong since I've looked at many articles/tutorials and they all said to do it this way.
val client = Retrofit.Builder()
.baseUrl("https://example.com/api/")
.addCallAdapterFactory(RxJava2CallAdapaterFactory.create())
.build()
.create(ApiService::class.java)
client.login(JSONObject().put("email", ...).toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
Could anyone explain to me what I'm actually doing wrong?
Edit:
I have tried the follow code and I still get the same result. No request is made.
val okHttpClient = OkHttpClient.Builder().addInterceptor(
HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEFAULT)
).build()
val client = Retrofit.Builder()
.baseUrl("https://example.com/api/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build()
.create(ApiClient::class.java)
client.login(JSONObject().put("email", ...).toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ user -> println(user) }, { error -> println(error) })
You are missing to add a Moshi converter to your retrofit instance.
.addConverterFactory(MoshiConverterFactory.create())
You should add a logging interceptor to see the logs.
val httpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
val builder = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(RxJava2CallAdapaterFactory.create())
val apiService = builder.client(httpClient).build().create(ApiService::class.java)
I assume that you add INTERNET permission to AndroidManifest.xml
As #sonnet suggests, you should add callbacks to your requests
apiService.login(JSONObject().put("email", ...).toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ user -> ... }, { error -> ... })