I have a list of URLs that I need to call and if I got success response for all of them, I can continue.
I can simply do it with RxJava and Retrofit like:
#PUT
fun uploadFile(#Url url: String, #Body file: RequestBody): Single<Response<Void>>
Observable.fromIterable(uploadUrls)
.flatMapSingle {
val requestBody = InputStreamRequestBody(contentResolver, uri)
upsellServiceApi.uploadFile(url, requestBody)
}
.toList()
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.subscribe(
{ responses ->
if (responses.all { it.isSuccessful }) {
// continue
} else {
//error
}
},
{
// error
}
Now I need to do the same thing without retrofit and by only using okhttpclient. How should I do it?
You could use a library like https://github.com/liujingxing/rxhttp to keep using RxJava. Or implement it yourself Using RxJava and Okhttp
If you don't want to use RxJava, then just enqueue and handle callback methods yourself.
Solved it
Observable.fromIterable(uploadUrls)
.map {
val request = Request.Builder().url(url).put(InputStreamRequestBody(contentResolver!!, uri)).build()
okHttpClient.newCall(request).execute()
}
.collect(
{
mutableListOf<Response>()
},
{
list: MutableList<Response>, response: Response -> list.add(response)
}
)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.subscribe(
{ responses ->
if (responses.all { it.isSuccessful }) {
// continue
} else {
// error
}
},
{
// error
}
).also { compositeDisposable.add(it) }
Related
Hello i want to use Multiple Base urls one ktor client instance . i was able to acheive it in Retrofit 2 . but stuck in Ktor 2.1.0
IN retrofit here is my interceptor :
#Singleton
#Provides
#Named("baseurl")
fun provideChangeBaseUrlInterceptor(controller: CLibController) = Interceptor { chain ->
var host = controller.getENTBaseUrlOnline().toHttpUrl()
var request: Request = chain.request()
when {
request.getAnnotation(HomeApi::class.java) == HomeApi() -> {
host = controller.getENTBaseUrlOnline().toHttpUrl()
}
request.getAnnotation(OffersApi::class.java) == OffersApi() -> {
host = controller.getOutletBaseUrlOnline().toHttpUrl()
}
request.getAnnotation(FavApi::class.java) == FavApi() -> {
host = controller.getOutletBaseUrlOnline().toHttpUrl()
}
request.getAnnotation(ProfileApi::class.java) == ProfileApi() -> {
host = controller.getAuthBaseUrlOnline().toHttpUrl()
}
}
var newUrl: HttpUrl? = null
try {
newUrl = request.url.newBuilder().scheme(host.scheme).host(host.toUrl().toURI().host)
.build()
} catch (e: URISyntaxException) {
e.printStackTrace()
}
assert(newUrl != null)
request = request.newBuilder().url(newUrl!!).build()
chain.proceed(request)
}
i have 7 base urls (Microservices backend) and each base url is accosicate with specific endpoints,SO i will acheive it with one instance of retrofit by categorize them with a custom annotations On my Api Class.
and get the Annotations in interceptor like the code above.Once i get the annotations of a request in interceptor i will changes it url to my desire url
Can any one help me achive this type of custom logic with ktor cleint (Single Client for multiple base urls )
Okay i was finally able to make multiple base urls handled with one client instance . just like custom annotations ktor client gives us attributes to send custom data in any request .
here is the ktor client code :
#Provides
#Singleton
fun provideKtor(jwtToken: String, cLibController: CLibController):
HttpClient =
HttpClient(Android) {
defaultRequest {
url {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
protocol = URLProtocol.HTTPS
host = cLibController.getENTBaseUrlOnline()
}
}
install(Logging) { level = LogLevel.ALL }
install(ContentNegotiation) { gson { } }
install(Auth) {
bearer {
loadTokens {
BearerTokens(jwtToken, jwtToken)
}
}
}
}.apply {
plugin(HttpSend).intercept { request ->
when (**request.attributes[MyAttributeKey]**) { // this is the key
"homeApi" -> request.url.host = cLibController.getENTBaseUrlOnline()
"profileApi" -> request.url.host = cLibController.getAuthBaseUrlOnline()
}
execute(request)
}
}
and setting my attribute key in each api class like :
client.post {
attributes.put(MyAttributeKey, "profileApi")
url {...}
}
I'm trying to get data using Retrofit call.enqueue ( DrawCircles() ), when i debug the values are there, but i think it does not waits and my function proceeds with the rest of lines of code. So the problem when I run it, the List of results (myListCoord ) is always null, how to make synchronous calls.
here is my code:
doAsync {
var a = DrawCircles()
myListCoord = a.runCircles()
}
fun runCircles(): List<Coordinates>? {
val request = ServiceBuilder.buildService(TmdbEndpoints::class.java)
val call = request.getCorrdinates()
call.enqueue(object : Callback<MyList> {
override fun onResponse(call: Call<MyList>, response: Response<MyList>) {
if (response.isSuccessful){
Toast.makeText(this#DrawCircles, "Succès", Toast.LENGTH_LONG).show()
myListCoord = response.body()!!.locations
}
}
override fun onFailure(call: Call<MyList>, t: Throwable) {
Toast.makeText(this#DrawCircles, "${t.message}", Toast.LENGTH_SHORT).show()
}
})
return myListCoord
}
Have you tried using call.execute() instead of call.enqueue() ?
From the docs:
void enqueue(Callback<T> callback)
Asynchronously send the request and notify callback of its response or if an error occurred talking to the server, creating the request, or processing the response.
Response<T> execute() throws IOException
Synchronously send the request and return its response.
(Emphasis mine)
I am using Retrofit. Using Kotlin. I need to know the resonse status code. Like is it 200 or 500. How can I get it from the response ?
My Api class:
interface Api {
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<Void>> }
This is how I am calling Api. But note that SERVE DOES NOT RETURN CODE FIELD IN RESPONSE BODY!
api.checkSmsCode(
CheckCodeBody(
code = code
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//HOW TO CHECK STATUS RESPONSE STATUS CODE HERE???
},
{ e ->
when (e) {
is IOException -> view?.showNoNetworkAlert()
else -> view?.invalidCodeError()
}
}
).also {}
As I understood, in Java it was a easy peasy thing.
You just use response.code() or something similar and that's it. But how to achieve it in Kotlin?
so your on response should look something like this
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
//
}
})
then inside that you should just to able to do
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
response.code()
}
})
is this what your talking about?
yo need to use it
interface OnlineStoreService{
#Headers("Content-Type: application/json","Connection: close")
#POST
fun getDevices(
#Url url: String,
#Header("Authorization") token: String,
#Body apiParams: APIParams
): Observable<OnlineStoresInfo>
}
.subscribe({ onlineStoresInfo -> // or it -> where "it" it's your object response, in this case is my class OnlineStoresInfo
loading.value = false
devices.value = onlineStoresInfo.devices
}, { throwable ->
Log.e(this.javaClass.simpleName, "Error getDevices ", throwable)
loading.value = false
error.value = context.getString(R.string.error_information_default_html)
})
.subscribe({ it ->
// code
}, { throwable ->
//code
})
If you haven't configure your retrofit request method to return a Response<*> you won't be able to have the response code. Example:
interface SomeApi{
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<String>>
}
And after you finish your request:
.subscribe({
//access response code here like : it.code()
//and you can access the response.body() for your data
//also you can ask if that response.isSuccessful
})
I am quite new to RxJava and I am tryign to implement and OAuth2 flow with refresh tokens in my Android/Kotlin app.
I got a bit stuck with refreshing my access tokens inside an interceptor (I append access token for each API request inside an OAuthAuthenticator). The problem is that I would like to wait with populating the request until the token request is completed.
Could someone hint me how to achieve this?
This is my authenticator class:
class OAuthAuthenticator(
private val authStateManager: AuthStateManager,
private val authRepository: AuthRepository): Authenticator {
override fun authenticate(route: Route?, response: Response?): Request? {
// handle invalid token
if (response != null && response.code() == 401) {
println("Invalid access token")
authRepository.refreshToken()
.subscribe { _ ->
println("Token refreshed")
}
}
// this should happen after the token request is completed since it would fail again otherwise
return response?.request()!!
.newBuilder()
.header("Authorization", "Bearer " + authStateManager.current.accessToken)
.build()
}
}
You are making an asyncronous call inside a synchronous method. That won't work. You will have to put your code that you want to be executed after token request has been completed inside the rx chain.
class OAuthAuthenticator(
private val authStateManager: AuthStateManager,
private val authRepository: AuthRepository) : Authenticator {
override fun authenticate(route: Route?, response: Response?): Single<Request> {
// handle invalid token
if (response != null && response.code() == 401) {
return Single.error(InvalidAccessTokenException("Invalid access token"))
}
return Single.fromCallable { authRepository.refreshAccessToken() }
.subscribeOn(Schedulers.computation())
.map {
return#map response?.request()!!
.newBuilder()
.header("Authorization", "Bearer " +
authStateManager.current.accessToken)
.build()
}
}
}
And then you can call it like this:
val authenticator = OAuthAuthenticator(authStateManager, authRepository)
val disposable = authenticator.authenticate(route, response)
.subscribe({ _ ->
println("Token refreshed")
}, { error ->
if (error is InvalidAccessTokenException) {
println("Invalid access token")
} else {
println("Could not refresh token. Signing out...")
authStateManager.signOut()
}
})
You can use blockingGet() to make the Single synchronous. But that's not advised. But since you create the Single locally with
Single.fromCallable { authRepository.refreshAccessToken() }
you could just call the appropriate method directly without wrapping it with Rx.
if (response != null && response.code() == 401) {
println("Invalid access token")
try {
authRepository.refreshAccessToken()
println("Token refreshed")
} catch(error: Throwable) {
println("Could not refresh token. Signing out...")
authStateManager.signOut()
return null
}
}
Recently I started using Retrofit 2 and I faced an issue with parsing empty response body. I have a server which responds only with http code without any content inside the response body.
How can I handle only meta information about server response (headers, status code etc)?
Edit:
As Jake Wharton points out,
#GET("/path/to/get")
Call<Void> getMyData(/* your args here */);
is the best way to go versus my original response --
You can just return a ResponseBody, which will bypass parsing the response.
#GET("/path/to/get")
Call<ResponseBody> getMyData(/* your args here */);
Then in your call,
Call<ResponseBody> dataCall = myApi.getMyData();
dataCall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response) {
// use response.code, response.headers, etc.
}
#Override
public void onFailure(Throwable t) {
// handle failure
}
});
If you use RxJava, then it's better to use Completable in this case
Represents a deferred computation without any value but only indication for completion or exception. The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)?
http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html
in the accepted answer:
#GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);
If the endpoint returns failure response code, it will still be in the onNext and you will have to check the response code yourself.
However, if you use Completable.
#GET("/path/to/get")
Completable getMyData(/* your args here */);
you will have only onComplete and onError.
if the response code is success it will fire the onComplete else it will fire onError.
If you are using rxjava, use something like :
#GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);
With kotlin, using the return type Call<Void> still throws IllegalArgumentException: Unable to create converter for retrofit2.Call<java.lang.Void>
Using Response instead of Call resolved the issue
#DELETE("user/data")
suspend fun deleteUserData(): Response<Void>
Here is an example Kotlin in MVVM with service, Repository and ViewModel:
Service:
#POST("/logout")
suspend fun logout(#Header("Authorization") token: String):Response<Unit>
Repository:
//logout
private val mLogoutResponse = MutableLiveData<String>()
val logoutResponse: LiveData<String>
get() {
return mLogoutResponse
}
suspend fun logout(token: String) {
try {
val result=quizzerProfileApi.logout(token)
if(result.code()!=0)
{
mLogoutResponse.postValue(result.code().toString())
}
} catch (e: Exception) {
Log.d("ProfileRepository", "logout: Error: $e")
}
}
ViewModel:
fun logout(token: String) {
viewModelScope.launch {
repository.logout(token)
}
}
val logoutResponseCd: LiveData<String>
get() = repository.logoutResponse
in Activity:
private fun logout() {
myViewModel.logout(token)
myViewModel.logoutResponseCd.observe(this, Observer {
if(it!="0"){
Log.d(TAG, "logout: code= $it")
finish()
}
else
Toast.makeText(this, "Error logging out: $it", Toast.LENGTH_SHORT).show()
})
}
Here is how I used it with Rx2 and Retrofit2, with PUT REST request:
My request had a json body but just http response code with empty body.
The Api client:
public class ApiClient {
public static final String TAG = ApiClient.class.getSimpleName();
private DevicesEndpoint apiEndpointInterface;
public DevicesEndpoint getApiService() {
Gson gson = new GsonBuilder()
.setLenient()
.create();
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpClientBuilder.addInterceptor(logging);
OkHttpClient okHttpClient = okHttpClientBuilder.build();
apiEndpointInterface = new Retrofit.Builder()
.baseUrl(ApiContract.DEVICES_REST_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(DevicesEndpoint.class);
return apiEndpointInterface;
}
The interface:
public interface DevicesEndpoint {
#Headers("Content-Type: application/json")
#PUT(ApiContract.DEVICES_ENDPOINT)
Observable<ResponseBody> sendDeviceDetails(#Body Device device);
}
Then to use it:
private void sendDeviceId(Device device){
ApiClient client = new ApiClient();
DevicesEndpoint apiService = client.getApiService();
Observable<ResponseBody> call = apiService.sendDeviceDetails(device);
Log.i(TAG, "sendDeviceId: about to send device ID");
call.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
#Override
public void onSubscribe(Disposable disposable) {
}
#Override
public void onNext(ResponseBody body) {
Log.i(TAG, "onNext");
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "onError: ", t);
}
#Override
public void onComplete() {
Log.i(TAG, "onCompleted: sent device ID done");
}
});
}
You can try this one
Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(new NullOnEmptyConverterFactory())
.client(okHttpClient).build();
class NullOnEmptyConverterFactory extends Converter.Factory {
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return (Converter<ResponseBody, Object>) body -> {
if (body.source().exhausted()) return null;
return delegate.convert(body);
};
}
}
Kotlin, Retrofit
#POST
suspend fun accountVerification(
#Body requestBody: RequestBody
): Response<Unit>
and success can be check using
if (response.isSuccessful) { }