Getting Header information with RXJava and Retrofit - android

I'm trying to convert my app which currently uses Retrofit, to use RX Java.
In order to handle Pagination, I traditionally was grabbing the nextPage URL from the response headers.
#Override
public void success(Assignment assignment, Response response) {
response.getHeaders(); // Do stuff with header info
}
However, since switching to RX Java, i'm not sure how to get the response information from my retrofit call.
#GET("/{item_id}/users")
Observable<List<Objects>> getObjects(#Path("object_id") long object_id);
#GET("/{next}")
Observable<List<Objects>> getNextPageObjects(#Path("next") String nextURL);
Is there a way to have my retrofit calls to return my header information along with my Typed objects?

You can use
Observable<Response>
as return type to get the response details
#GET("/{item_id}/users")
Observable<Response> getObjects(#Path("object_id") long object_id);
#GET("/{next}")
Observable<Response>getNextPageObjects(#Path("next") String nextURL);
This is how the Response object would look like
You would have to then parse the headers and body from the observable
serviceClass.getNextPageObjects("next").flatMap(new Func1<Response, Observable<List<Objects>>() {
#Override
public Observable<AuthState> call(Response response) {
List<Header> headers = response.getHeaders();
GsonConverter converter = new GsonConverter(new Gson());
// you would have to change this to convert the objects to list
List<Objects> list = converter.fromBody(response.getBody(),
YourClass.class);
return Observable.from(list);
}
}

Let me spoiler this. You could intercept request and response as of OkHttp-2.2. As on OkHttp wiki says interceptors will not work in Retrofit with OkUrlFactory. You need to provide your Client implementation to execute Retrofit requests on this custom Client and directly on OkHttp
Unfortunately, it is not out yet (soon).
public class ResponseHeaderInterceptor implements Interceptor {
public interface ResponseHeaderListener{
public void onHeadersIntercepted(Headers headers);
}
private ResponseHeaderListener mListener;
public ResponseHeaderInterceptor(){};
public ResponseHeaderInterceptor(ResponseHeaderListener listener){
mListener = listener;
}
#Override public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if(mListener != null){
mListener.onHeadersIntercepted(response.headers());
}
return response;
}
Usage:
ResponseHeaderListener headerListener = new ResponseHeaderListener(){
#Override
public void onHeadersIntercepted(Headers headers){
//do stuff with headers
}
};
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new ResponseHeaderInterceptor(headerListener));
RestAdapter.Builder restAdapterBuilder = new RestAdapter.Builder();
restAdapterBuilder.setClient(new OkHttpClient22(okHttpClient));
This is application interceptor and will be called only once.
Please note that I did all this by logic, I still don't have OkHttp-2.2. Just read about it here. I'll remove some of this text when 2.2 is latest jar.
Alternatively, you can try to create custom client and with one interface deliver the response:
public class InterceptableClient extends OkClient {
private ResponseListener mListener;
public interface ResponseListener{
public void onResponseIntercepted(Response response);
}
public InterceptableClient(){};
public InterceptableClient(ResponseListener listener){
mListener = listener;
}
#Override
public retrofit.client.Response execute(Request request) throws IOException {
Response response = super.execute(request);
if(mListener != null) //runs on the executor you have provided for http execution
mListener.onResponseIntercepted(response);
return response;
}
}
Edit: OkHttp 2.2 has been released.

Related

How can I avoid request parameter names getting encoded when making a form-urlencoded Get request in Retrofit?

I am currently developing android app which uses Retrofit & OkHttpClient to get/send data from the server.
That was great when calling my own server, while it runs into 404 error when trying to call google map api.
The following represents response with error.
Response{protocol=h2, code=404, message=, url=https://maps.googleapis.com/maps%2Fapi%2Fgeocode%2Fjson%3Fkey=defesdvmdkeidm&latlng=11.586215,104.893197}
This is obviously because '/' and '?' was encoded into "%2F" and "%3F".
The solution could be prevent urlencode for those special characters, but couldn't make it.
What I tried is add custom header "Content-Type:application/x-www-form-urlencoded; charset=utf-8" to OkHttpClient via intercepter but that does not work.
Best detailed response will be appreciated.
Regards.
private Retrofit createRetrofit(OkHttpClient client, String _baseUrl) {
return new Retrofit.Builder()
.baseUrl(_baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
}
private Retrofit createGoogleRetrofit() {
return createRetrofit(createGoogleClient(), baseUrl);
}
public DenningService getGoogleService() {
_baseUrl = "https://maps.googleapis.com/";
final Retrofit retrofit = createGoogleRetrofit();
return retrofit.create(DenningService.class);
}
public interface DenningService {
#GET("{url}")
#Headers("Content-Type:application/x-www-form-urlencoded; charset=utf-8")
Single getEncodedRequest(#Path("url") String url);
}
private void sendRequest(final CompositeCompletion completion, final ErrorHandler errorHandler) {
mCompositeDisposable.add(mSingle.
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Function() {
#Override
public JsonElement apply(JsonElement jsonElement) throws Exception {
return jsonElement;
}
})
.subscribeWith(new DisposableSingleObserver() {
#Override
public void onSuccess(JsonElement jsonElement) {
completion.parseResponse(jsonElement);
}
#Override
public void onError(Throwable e) {
if (e instanceof HttpException && ((HttpException) e).code() == 410) {
errorHandler.handleError("Session expired. Please log in again.");
} else {
errorHandler.handleError(e.getMessage());
}
e.printStackTrace();
}
})
);
}
public void sendGoogleGet(String url, final CompositeCompletion completion) {
mSingle = getGoogleService().getEncodedRequest(url);
sendRequest(completion, new ErrorHandler() {
#Override
public void handleError(String error) {
ErrorUtils.showError(context, error);
}
});
}
The problem is in the definition of your Retrofit service interface and the values you pass to it.
public interface DenningService {
#GET("{url}")
#Headers("Content-Type:application/x-www-form-urlencoded; charset=utf-8")
Single getEncodedRequest(#Path("url") String url);
}
From what you've posted, I'm going to assume the value of url is:
maps/api/geocode/json?key=defesdvmdkeidm&latlng=11.586215,104.893197
Here's how it should look:
public interface DenningService {
#FormUrlEncoded
#GET("/maps/api/geocode/json")
Single getEncodedRequest(#Field("key") String key,
#Field("latlng") String latlng);
}
And then you'd call it like this:
mSingle = getGoogleService().getEncodedRequest(key, latlng);
Of course, you will have to figure out how to separate the key and latlng parameters out of the current url string.
Edit
It's not obvious to me whether or not you actually want your request to be application/x-www-form-urlencoded, or if you were just trying that to see if it solved your problem. If you do not want it, then your interface would look like this instead:
public interface DenningService {
#GET("/maps/api/geocode/json")
Single getEncodedRequest(#Query("key") String key,
#Query("latlng") String latlng);
}

Android i get NULL when i post data with Retrofit

I'm novice on using Retrofit, I want to post data as an json data with object format to server and get response from that, I tested my restful url with fake data and that work fine without any problem, but when i post data from android i get null. what i want to do? i want to post data to server and get response with this format:
public class UserLoginInformation {
private String username;
private String userUniqueId;
}
My interface:
public interface SignalRetrofitServiceProviders {
#POST("joinUserToApplication")
Call<List<UserLoginInformation>> joinUserToApplication(#Body Object data);
}
post data:
private void joinUserToApplication(String data) {
AlachiqRestFullProvider signalProvider = new AlachiqRestFullProvider();
SignalRetrofitServiceProviders signalRetrofitServiceProviders = signalProvider.getServices();
Call<List<UserLoginInformation>> call = signalRetrofitServiceProviders.joinUserToApplication(data);
call.enqueue(new Callback<List<UserLoginInformation>>() {
#Override
public void onResponse(Call<List<UserLoginInformation>> call, Response<List<UserLoginInformation>> response) {
List<UserLoginInformation> result = response.body();
final String r = new Gson().toJson(result);
}
#Override
public void onFailure(Call<List<UserLoginInformation>> call, Throwable t) {
t.printStackTrace();
Log.e("onFailure ", t.getMessage());
}
});
}
RestFull provider:
public class AlachiqRestFullProvider {
private SignalRetrofitServiceProviders signalRetrofitServiceProviders;
public AlachiqRestFullProvider() {
OkHttpClient httpClient = new OkHttpClient();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ClientSettings.ALACHIQ_WEB_BASE_URL)
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
signalRetrofitServiceProviders = retrofit.create(SignalRetrofitServiceProviders.class);
}
public SignalRetrofitServiceProviders getServices() {
return signalRetrofitServiceProviders;
}
}
data for post:
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("mobileNumber", mobileNumber);
jsonObject.put("userUniqueId", uuid);
jsonObject.put("userPhoneNumbers", phoneContacts);
startService(
new Intent(context, AlachiqRestFullWebServiceProvider.class)
.putExtra("request_type", "joinUserToApplication")
.putExtra("data", jsonObject.toString()));
} catch (JSONException e) {
e.printStackTrace();
}
server response data like with this format:
{"username":"mahdi","userUniqueId":"fwcrwcrwr23234c24"}
server side application to get data is:
Route.post('joinUserToApplication', function *(request, response) {
console.log(request._raw);
response.send({username: "mahdi", userUniqueId: "fwcrwcrwr23234c24"});
});
The POST body that is being serialized is a generic Object.
Create a POJO with the fields that you require and use a deserializer that retrofit understands
public interface SignalRetrofitServiceProviders {
#POST("joinUserToApplication")
Call<List<UserLoginInformation>> joinUserToApplication(#Body UserLoginInformation data);
}
Please note the parameter of the function is not changed to UserLoginInformation
http://square.github.io/retrofit/#restadapter-configuration

How to get different objects with Retrofit Android

I'm using Retrofit 1.9.0 in Android Studio to get a response from my REST API.
The method I want to do is GET, on this URL: http://dotfreeride.com/api/rest/adventures.php
I successfully retrieved the response for another API but that had only 1 object, this has 3 big objects.
My IApiMethods interface is like this:
#GET("/adventures.php")
JSONObject getAdventures(
Callback<AdventuresApi> cb
);
My AdventuresApi (Model class) is like this:
public class AdventuresApi {
public String adventure_id;
public String trimaps_context;
public String name;
public String video_url;
public List<ArrayPoi> array_poi;
public class ArrayPoi {
String poi_id;
String name;
String lat;
String lng;
String video_url;
}
}
My Retrofit call in the Activity is like this:
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.build();
IApiMethods methods = restAdapter.create(IApiMethods.class);
Callback callback = new Callback() {
#Override
public void success(Object o, Response response) {
}
#Override
public void failure(RetrofitError error) {
Log.e("JSON", "NO DATA!");
}
};
methods.getAdventures(callback);
I don't really know how to get the objects, I want to get the name of the object (Example: where trimaps_context is "verb", I need the name "Powder Hound")
For a single object I successfully did it like this in onResponse(Object o, Response response):
(ProfileApi) profileData = (ProfileApi) o;
Log.e("JSON", profileData.name + " " + profileData.email);
1) You are trying to combine both synchronous and asynchronous call. If you want to perform a request asynchronously you have to define it like this:
#GET("/adventures.php")
void getAdventures(
Callback<List<AdventuresApi>> cb
);
2) Do not create RestAdapter instance everytime you call request. It's really heavyweight operation. Use singleton pattern. You can then simply call:
ApiManager.getAdapter().getAdventures(...);
3) Object mapping is provided by parametrized Callback class:
ApiManager.getAdapter().getAdventures(
new Callback<List<AdventuresApi>>() {
#Override
public void success(List<AdventuresApi> adventures, Response response) {
// here you can access the adventures list
}
#Override
public void failure(RetrofitError error) {
// handle error
}
});

Retrofit + Okhttp cancel operation not working

I am using retrofit in my application like this
final OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new YourInterceptor());
final OkClient okClient = new OkClient(okHttpClient);
Builder restAdapterBuilder = new RestAdapter.Builder();
restAdapterBuilder.setClient(okClient).setLogLevel(LogLevel.FULL)
.setEndpoint("some url");
final RestAdapter restAdapter = restAdapterBuilder.build();
public class YourInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
// TODO Auto-generated method stub
Request request = chain.request();
if (request != null) {
Request.Builder signedRequestBuilder = request.newBuilder();
signedRequestBuilder.tag("taggiventorequest");
request = signedRequestBuilder.build();
request.tag();
}
return chain.proceed(request);
}
}
after sending request i am calling
okHttpClient.cancel("taggiventorequest");
but request is not cancelling i am getting the response from retrofit
dont know why it is not cancelling my request
I need volley like cancelation retrofit
As Retrofit API Spec, Canceling request will be included in version 2.0.
cancel() is a no-op after the response has been received. In all other
cases the method will set any callbacks to null (thus freeing strong
references to the enclosing class if declared anonymously) and render
the request object dead. All future interactions with the request
object will throw an exception. If the request is waiting in the
executor its Future will be cancelled so that it is never invoked.
For now, you can do it by creating custom callback class which implements on Callback from retrofit.
public abstract class CancelableCallback<T> implements Callback<T> {
private boolean canceled;
private T pendingT;
private Response pendingResponse;
private RetrofitError pendingError;
public CancelableCallback() {
this.canceled = false;
}
public void cancel(boolean remove) {
canceled = true;
}
#Override
public void success(T t, Response response) {
if (canceled) {
return;
}
onSuccess(t, response);
}
#Override
public void failure(RetrofitError error) {
if (canceled) {
return;
}
onFailure(error);
}
protected abstract void onSuccess(T t, Response response);
protected abstract void onFailure(RetrofitError error);
}
MyApi.java,
private interface MyApi {
#GET("/")
void getStringList(Callback<List<String>> callback);
}
In Activity or Fragment,
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(Config.URL)
.build();
MyApi service = restAdapter.create(MyApi.class);
CancelableCallback callback = new CancelableCallback<List<String>>() {
#Override
protected void onSuccess(List<String> stringList, Response response) {
for (String str : stringList) {
Log.i("Result : ", str);
}
}
#Override
protected void onFailure(RetrofitError error) {
Log.e("Error : ", error.getMessage() + "");
}
};
service.getStringList(callback);
To cancel your request, simple call
callback.cancel();
This is an simple example to cancel each request. You can handle (cancel, pause, resume) two or more request at the same time by creating callback manager class. Please take a look that comment for reference.
Hope it will be useful for you.
The problem is that the request is completed, from the docs:
http://square.github.io/okhttp/javadoc/com/squareup/okhttp/OkHttpClient.html#cancel-java.lang.Object-
cancel
public OkHttpClient cancel(Object tag)
Cancels all scheduled or in-flight calls tagged with tag. Requests that are already complete cannot be canceled.

RETROFIT & AUTH COOKIE

I need to know how to add an authorization cookie header in retrofit. I have seen advice like using request intercepter etc. Below is what I am trying, but is this correct? First of all I already needed a RequestAdatper to get the session id the first time around. This can only be set by the builder of the request adapter. But I needed to make a request just to get the session id in the first place. Do I need two rest adapters one to get the sessionId and another one after I have obtained it. What I really need is a method on adapter to set the cookie after I get it but it does not appear to be such a method. This is getting awkward. How do I set authorization cookie in retrofit? I don't see this in FAQ or tutorials.
RequestInterceptor requestInterceptor = new RequestInterceptor()
{
#Override
public void intercept(RequestFacade request) {
request.addHeader("Set-Cookie", "sessionId="+sessionIdentifier);
}
};
RestAdapter.Builder().setServer(serverURL)..setRequestIntercepter(requestIntercepter).build();
// but I don't have sessionId when this is first issued ???
Keep a reference to the interceptor and treat it as a singleton like you would be RestAdapter itself.
public class ApiHeaders implements RequestInterceptor {
private String sessionId;
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public void clearSessionId() {
sessionId = null;
}
#Override public void intercept(RequestFacade request) {
if (sessionId != null) {
request.setHeader(...);
}
}
}
Now, simply call setSessionId after your authentication call. All subsequent requests will include the header.
You can get the cookies like this
public class MyCookieManager extends CookieManager {
#Override
public void put(URI uri, Map<String, List<String>> stringListMap) throws IOException {
super.put(uri, stringListMap);
if (stringListMap != null && stringListMap.get("Set-Cookie") != null)
for (String string : stringListMap.get("Set-Cookie")) {
if (string.contains("JSESSIONID")) {
Preference.getInstance().setSessionId(string);
}
}
}
}
Use this to set the CookieHandler
MyCookieManager myCookieManager = new MyCookieManager();
CookieHandler.setDefault(myCookieManager);
and then use it like this in your request Interceptor
String sessionId = preference.getSessionId();
if (sessionId != null)
requestFacade.addHeader(Cookie, sessionId);
Step 1. Parse Response headers.
Call this method inside your Callback in overriden success method.
/**
* Method extracts cookie string from headers
* #param response with headers
* #return cookie string if present or null
*/
private String getCookieString(Response response) {
for (Header header : response.getHeaders()) {
if (null!= header.getName() && header.getName().equals("Set-Cookie")) {
return header.getValue();
}
}
return null;
}
Step 2. Write some static class or singleton to keep cookies and your RequestInterceptor instance. Inside RequestInterceptor override intercept method to add your cookies to Header.
public class RestAdapter {
private static String cookies;
public static String getCookies() {
return cookies;
}
public static void setCookies(String cookies) {
RestAdapter.cookies = cookies;
}
/**
* Injects cookies to every request
*/
private static final RequestInterceptor COOKIES_REQUEST_INTERCEPTOR = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
if (null != cookies && cookies.length() > 0) {
request.addHeader("Cookie", cookies);
}
}
};
public static final RestInterface getService() {
return new RestAdapter.Builder()
.setEndpoint(Config.ENDPOINT)
.setRequestInterceptor(COOKIES_REQUEST_INTERCEPTOR)
.setConverter(new JacksonConverter())
.setLogLevel(RestAdapter.LogLevel.NONE)
.build()
.create(RestInterface.class);
}
}
According to #sbtgE's answer, but with some corrections. CookieHandler.getDefault() may be null, so I use CookieManager.
app's build.gradle:
dependencies {
...
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.4.1'
}
Setting up Retrofit:
service = new Retrofit.Builder()
.baseUrl(/* your base URL */)
.addConverterFactory(/* your favourite converter */)
.client(
new OkHttpClient.Builder()
// this line is the important one:
.cookieJar(new JavaNetCookieJar(new CookieManager()))
.build())
.build()
.create(YourInterface.class);
Simple solution using lib. compile com.squareup.okhttp3:okhttp-urlconnection:3.2.0.
JavaNetCookieJar jncj = new JavaNetCookieJar(CookieHandler.getDefault());
OkHttpClient.Builder().cookieJar(jncj).build();

Categories

Resources