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 -> ... })
Related
I'm learning Kotlin Coroutines and I'm trying to build a simple app with some API requests.
Unfortunately I've stumbled upon an error which is not really talkative, this is all I have in the logs:
FATAL EXCEPTION: main
Process: com.tests.myapp, PID: 14743
This is my simple coroutine which would simply call an API endpoint. I've copied the syntax from this tutorial.
CoroutineScope(Dispatchers.Main).launch {
API.call().registration();
}
For Kotlin Coroutines I use this version:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
And for the networking library I have Retrofit like this:
object API {
private const val BASE_URL = "http://my-test-url-comes-here.com"
private val okHttpClient = OkHttpClient()
.newBuilder()
.addInterceptor(RequestInterceptor)
.build()
private fun getClient(): Retrofit =
Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
fun call(): Endpoints {
return getClient().create(Endpoints::class.java)
}
}
Any insights?
I think you should use Dispatchers.IO because you are calling a functon that uses network. by passing Dispatcher.Main, you are asking coroutinScope to use UI thread. that gives a Network on Main thread Exception.
so,
CoroutineScope(Dispatchers.IO /* replace Main with IO here */).launch {
API.call().registration();
}
try to use Dispatchers.IO and also I would suggest you to create your api service only once like this and check you manifest file - you should add internet permission to your app, to make network calls
object API {
private const val BASE_URL = "http://my-test-url-comes-here.com"
private val okHttpClient = OkHttpClient()
.newBuilder()
.addInterceptor(RequestInterceptor)
.build()
private val client by lazy {
Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val api by lazy {
client.create(Endpoints::class.java)
}
}
NOTE: the lazy initializer I wrote here is just sample you can also you non-lazy initializer
I am writing an android application using Kotlin ,Retrofit ,OKHttp and Rxjava when make a call to my API in order to add a new user to database i get the following message in logcat:
I/system_server: oneway function results will be dropped but finished with status OK and parcel size 4
The problem is that nothing happens no user is added in fact the API does not receive any requests from my app even thought i have the appropriate permissions and a android:usesCleartextTraffic="true" annotation in my AndroidManifest.xml.
By putting println() in various places in the code I was able to confirm that the function responsible for making the call is in fact executed but code responsible for handling response is never called and no error is reported.
Here is the code of above mentioned function:
private fun addUser(user: UserDataObject) {
val client = OkHttpClient.Builder()
.addInterceptor(BasicAuthInterceptor("admin", "admin")) // temporarily hardcoded
.build()
val requestInterface = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.client(client)
.build().create(GetData::class.java)
myCompositeDisposable?.add(
requestInterface.addUser(user)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse, this::onError)
)
}
and the code of the interface:
interface GetData {
#POST("/api/some/API/URL")
fun addUser(#Body user: UserDataClass) : Completable}
also ( just in case ) the code of interceptor:
class BasicAuthInterceptor(username: String, password: String): Interceptor {
private var credentials: String = Credentials.basic(username, password)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
var request = chain.request()
request = request.newBuilder().header("Authorization", credentials).build()
return chain.proceed(request)
}
}
Can you please tell me why is my app not able to communicate with API ?
While searching for answer on the internet i found this question so I gather that it has something to do with selinux permissions but this doesn't help me at all since I'm just a beginner.
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 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.
I am using paging library version 2.1.0 in with java. How I can manage false status or empty array from API. I tried finding some solution but didn't get any.
If you're using Retrofit, you could use Interceptor to intercept error code.
Below is the code to handle response code error 401. Similarly, you can handle any response code.
var retrofit:Retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.build()
private fun getOkHttpClient() : OkHttpClient{
val okHttpCLient = OkHttpClient.Builder()
.addInterceptor(object : Interceptor{
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if(response.code() == 401){
Log.e(TAG,"Un-Authorized user")
}
return response
}
})
return okHttpCLient.build()
}
//OkHttp Library in Gradle file:
implementation 'com.squareup.okhttp3:okhttp:3.12.0'