Sending a JSON parameter to service - android

I want to use rxjava and retrofit.
this is my retrofit builder:
Retrofit provideRetrofit(OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(UrlManager.API_HOST)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
and okHttpClient:
public OkHttpClient client(HttpLoggingInterceptor loggingInterceptor, Cache cache) {
return new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(loggingInterceptor)
.connectTimeout(10, TimeUnit.SECONDS)
.build();
}
In my Login Activity I insert my user and password and when loginButton is clicked this method in called:
public DisposableObserver observeLoginButton() {
return view.observeLoginBtn()
.subscribeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<Object>() {
#Override
public void accept(Object o) throws Exception {
view.loadButtonAnimation(); //load animation
}
})
.map(new Function<Object, String>() {
#Override
public String apply(Object o) throws Exception {
return view.getUserAndPassword(); // get userName and password as a string from edittext
}
})
.switchMap(new Function<String, Observable<String>>() {
#Override
public Observable<String> apply(String s) throws Exception {
String[] info = s.split(" "); // split user name and pass
return model.getLoginCookie(info[0],info[1]); // send userName and pass to model and call my service
}
})
.observeOn(Schedulers.io())
.subscribeWith(view.observer());
}
For test i have inserted myself login info to sending a service. this is my getLoginCookie method:
public Observable<String> getLoginCookie(String userName, String password) {
Map<String, Object> obj = new ArrayMap<>();
obj.put("username", "JuJzWgbsDJ0lUlFYVzoxWg");
obj.put("password", "DD0vCYmzJuIPff9iKUpfQA");
obj.put("customCredential", "6298f927-f98a-44eb-a312-780674a76245,Mobile89954324581380882887");
obj.put("isPersistent", false);
RequestBody body = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"),
(new JSONObject(obj)).toString());
return service.getAuthentication(body);
}
My service work as a json parametr.So in order to i have used RequestBody to convert my map to json. Then i have called my service :
#POST(UrlManager.AUTHENTICATION+"Login")
Observable<String> getAuthentication(#Body RequestBody params);
When i run my app and click on login button i got this:
D/Payesh: --> POST http://****/Authentication.svc/json/Login (183-byte body)
W/System.err: remove failed: ENOENT (No such file or directory) : /data/data/com.groot.payesh/cache/okhttp_cache/journal.tmp
D/Payesh: <-- HTTP FAILED: android.os.NetworkOnMainThreadException
I/----->: apply: null
1- Why I got android.os.NetworkOnMainThreadException ?
2- How could i trace that my parameter is correct is send to my web service and does connect is established or not? I have just got java.lang.NullPointerException: The supplied value is null on onError(Throwable e) in observer.

Why I got android.os.NetworkOnMainThreadException ?
You are subscribing on AndroidSchedulers.mainThread() and observing on Schedulers.io(). That means you are doing network request on Main Thread. Switch Thread before network request by adding .observeOn(Schedulers.io()) before switchMap. And finally Observe On Main Thread if you are doing any view related task after finish.
return view.observeLoginBtn()
.subscribeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<Object>() {
#Override
public void accept(Object o) throws Exception {
view.loadButtonAnimation(); //load animation
}
})
.map(new Function<Object, String>() {
#Override
public String apply(Object o) throws Exception {
return view.getUserAndPassword(); // get userName and password as a string from edittext
}
})
.observeOn(Schedulers.io())
.switchMap(new Function<String, Observable<String>>() {
#Override
public Observable<String> apply(String s) throws Exception {
String[] info = s.split(" "); // split user name and pass
return model.getLoginCookie(info[0],info[1]); // send userName and pass to model and call my service
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(view.observer());
For
remove failed: ENOENT (No such file or directory) : /data/data/com.groot.payesh/cache/okhttp_cache/journal.tmp
make sure hoy have successfully created FIle for cache.
How could i trace that my parameter is correct is send to my web
service and does connect is established or not?
As you have used HttpLoggingInterceptor this will print all info about your request like Header, Body, Response.

Related

Good design for common success / failure / error handling for multiple APIs using Retrofit Android

I want to design API calls in such a way that it will be easy to handle success and failure responses easily from one place (instead of writing same code of call function for all APIs)
Here are the scenarios which I want to consider.
Handle success / failure and error responses like 4xx, 5xx etc of all APIs at one central place.
Want to cancel enqueue requests and also stop processing response if request is already sent in case of logout (because response parsing will modify some global data of app)
If access token has expired and 401 response received from cloud, it should get new token and then call API again automatically with new token.
My current implementation is not satisfying above requirements.
Is there any way to implement API calls which satisfy above requirements using Retrofit ?
Please suggest me a good design for this.
Here is my current implementation :
ApiInterface.java - It is an interface which contains different API calls definitions.
ApiClient.java - To get retrofit client object to call APIs.
ApiManager.java - It has methods to call APIs and parse their responses.
ApiInterface.java
public interface ApiInterface {
// Get Devices
#GET("https://example-base-url.com" + "/devices")
Call<ResponseBody> getDevices(#Header("Authorization) String token);
// Other APIs......
}
ApiClient.java
public class ApiClient {
private static Retrofit retrofitClient = null;
static Retrofit getClient(Context context) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), systemDefaultTrustManager())
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
retrofitClient = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
}
ApiManager.java
public class ApiManager {
private static ApiManager apiManager;
public static ApiManager getInstance(Context context) {
if (apiManager == null) {
apiManager = new ApiManager(context);
}
return apiManager;
}
private ApiManager(Context context) {
this.context = context;
apiInterface = ApiClient.getClient(context).create(ApiInterface.class);
}
public void getDevices(ResponseListener listener) {
// API call and response handling
}
// Other API implementation
}
Update :
For 1st point, interceptor will be helpful to handle 4xx, 5xx responses globally according to this.
But interceptor will be in the ApiClient file and to inform UI or API caller component, need to pass success or failure result in callback I mean response listener.
How can I do that ? Any idea ?
For 3rd point, I know little bit about Retrofit Authenticator. I think for this point it is suitable but it requires synchronous call to get new token using refresh token.
How can I make asynchronous call to synchronous ? (Note : this call is not retrofit call)
By handling the success/failure responses at a central place I'll assume you want to get rid of repeated boilerplate based on the error parsing logic and how it may create UI side-effects for your app.
I'd probably suggest keeping things really simple by creating a custom abstraction for Callback which invokes your APIs for success/failure according to your domain logic.
Here's something fairly simple implementation for use-case (1) :
abstract class CustomCallback<T> implements Callback<T> {
abstract void onSuccess(T response);
abstract void onFailure(Throwable throwable);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
onSuccess(response.body());
} else {
onFailure(new HttpException(response));
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
onFailure(t);
}
}
For use-case (2), to be able to cancel all enqueued calls upon a global event like logout you'd have to keep a reference to all such objects. Fortunately, Retrofit supports plugging in a custom call factory okhttp3.Call.Factory
You could use your implementation as a singleton to hold a collection of calls and in the event of a logout notify it to cancel all requests in-flight. Word of caution, do use weak references of such calls in the collection to avoid leaks/references to dead calls. (also you might want to brainstorm on the right collection to use or a periodic cleanup of weak references based on the transactions)
For use-case (3), Authenticator should work out fine since you've already figured out the usage there are 2 options -
Migrate the refresh token call to OkHttp/Retrofit and fire it synchronously
Use a count-down latch to make the authenticator wait for the async call to finish (with a timeout set to connection/read/write timeout for the refresh token API call)
Here's a sample implementation:
abstract class NetworkAuthenticator implements Authenticator {
private final SessionRepository sessionRepository;
public NetworkAuthenticator(SessionRepository repository) {
this.sessionRepository = repository;
}
public Request authenticate(#Nullable Route route, #NonNull Response response) {
String latestToken = getLatestToken(response);
// Refresh token failed, trigger a logout for the user
if (latestToken == null) {
logout();
return null;
}
return response
.request()
.newBuilder()
.header("AUTHORIZATION", latestToken)
.build();
}
private synchronized String getLatestToken(Response response) {
String currentToken = sessionRepository.getAccessToken();
// For a signed out user latest token would be empty
if (currentToken.isEmpty()) return null;
// If other calls received a 401 and landed here, pass them through with updated token
if (!getAuthToken(response.request()).equals(currentToken)) {
return currentToken;
} else {
return refreshToken();
}
}
private String getAuthToken(Request request) {
return request.header("AUTHORIZATION");
}
#Nullable
private String refreshToken() {
String result = null;
CountDownLatch countDownLatch = new CountDownLatch(1);
// Make async call to fetch token and update result in the callback
// Wait up to 10 seconds for the refresh token to succeed
try {
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
abstract void logout();
}
I hope this helps with your network layer implementation
So, With the help of official sample in the retrofit github repository here: https://github.com/square/retrofit/blob/fbf1225e28e2094bec35f587b8933748b705d167/samples/src/main/java/com/example/retrofit/ErrorHandlingAdapter.java
The ErrorHandlingAdapter is the closest you can get to your requirement because it lets you control enqueuing of the call, creating the error callbacks, calling error callbacks on your own. Whether you want the caller to do some action or you want to handle it yourself in one place or just both.
So this is how you can create it. Do read the inline comments to understand.
public final class ErrorHandlingAdapter {
/**
* Here you'll decide how many methods you want the caller to have.
*/
interface MyCallback<T> {
void success(Response<T> response);
void error(String s);
}
/**
* This is your call type
*/
interface MyCall<T> {
void cancel();
void enqueue(MyCallback<T> callback);
#NotNull
MyCall<T> clone();
}
public static class ErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
#Override
public #Nullable
CallAdapter<?, ?> get(
#NotNull Type returnType, #NotNull Annotation[] annotations, #NotNull Retrofit retrofit) {
if (getRawType(returnType) != MyCall.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalStateException(
"MyCall must have generic type (e.g., MyCall<ResponseBody>)");
}
Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
Executor callbackExecutor = retrofit.callbackExecutor();
return new ErrorHandlingCallAdapter<>(responseType, callbackExecutor);
}
private static final class ErrorHandlingCallAdapter<R> implements CallAdapter<R, MyCall<R>> {
private final Type responseType;
private final Executor callbackExecutor;
ErrorHandlingCallAdapter(Type responseType, Executor callbackExecutor) {
this.responseType = responseType;
this.callbackExecutor = callbackExecutor;
}
#NotNull
#Override
public Type responseType() {
return responseType;
}
#Override
public MyCall<R> adapt(#NotNull Call<R> call) {
return new MyCallAdapter<>(call, callbackExecutor);
}
}
}
static class MyCallAdapter<T> implements MyCall<T> {
private final Call<T> call;
private final Executor callbackExecutor;
MyCallAdapter(Call<T> call, Executor callbackExecutor) {
this.call = call;
this.callbackExecutor = callbackExecutor;
}
#Override
public void cancel() {
call.cancel();
}
#Override
public void enqueue(final MyCallback<T> callback) {
if (!SomeCondition.myCondition) {
// Don't enqueue the call if my condition doesn't satisfy
// it could be a flag in preferences like user isn't logged in or
// some static flag where you don't want to allow calls
return;
}
call.clone().enqueue(
new Callback<T>() {
#Override
public void onResponse(#NotNull Call<T> call, #NotNull Response<T> response) {
callbackExecutor.execute(() -> {
int code = response.code();
if (code >= 200 && code < 300) {
//success response
callback.success(response);
} else if (code == 401) {
// Unauthenticated so fetch the token again
getTheTokenAgain(callback);
} else if (code >= 400 && code < 500) {
//handle error the way you want
callback.error("Client error");
} else if (code >= 500 && code < 600) {
//handle error the way you want
callback.error("Server error");
} else {
//handle error the way you want
callback.error("Something went wrong");
}
});
}
#Override
public void onFailure(#NotNull Call<T> call, #NotNull Throwable t) {
callbackExecutor.execute(() -> {
if (t instanceof IOException) {
callback.error("IOException");
} else {
callback.error("Some exception");
}
});
}
});
}
private void getTheTokenAgain(MyCallback<T> callback) {
// Make the call to get the token & when token arrives enqueue it again
// Don't forget to put termination condition like 3 times, if still not successful
// then just log user out or show error
// This is just dummy callback, you'll need to make a
// call to fetch token
new MyTokenCallback() {
#Override
public void onTokenArrived(String token) {
//enqueue(callback); here
}
#Override
public void onTokenFetchFailed() {
callbackExecutor.execute(() -> {
callback.error("Counld't fetch token");
});
}
};
// This is for demo you should put it in success callback
SomeCondition.callCount++;
Log.d("MG-getTheTokenAgain", "Method called");
if (SomeCondition.callCount < 3) {
enqueue(callback);
} else {
callbackExecutor.execute(() -> {
callback.error("Counld't fetch token");
});
}
}
#NotNull
#Override
public MyCall<T> clone() {
return new MyCallAdapter<>(call.clone(), callbackExecutor);
}
}
}
This is how you'll plug this adapter:
private void makeApiCall() {
//This is just for demo to generate 401 error you won't need this
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
Request request = chain.request().newBuilder()
.addHeader("Accept","application/json")
.addHeader("Authorization", "cdsc").build();
return chain.proceed(request);
});
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl("http://httpbin.org/")
.addCallAdapterFactory(new ErrorHandlingAdapter.ErrorHandlingCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
HttpBinService service = retrofit.create(HttpBinService.class);
ErrorHandlingAdapter.MyCall<Ip> ip = service.getIp();
ip.enqueue(
new ErrorHandlingAdapter.MyCallback<Ip>() {
#Override
public void success(Response<Ip> response) {
Log.d("MG-success", response.toString());
}
#Override
public void error(String s) {
Log.d("MG-error", s);
}
});
}
You might have to bend some things to your needs, but I think this could be good reference because it's in the official sample.
1. Handle success / failure and error responses like 4xx, 5xx etc of
all APIs at one central place.
Create following two classes:
ApiResponse.kt
class ApiResponse<T : Any> {
var status: Boolean = true
var message: String = ""
var data: T? = null
}
ApiCallback.kt
abstract class ApiCallback<T : Any> : Callback<ApiResponse<T>> {
abstract fun onSuccess(response: ApiResponse<T>)
abstract fun onFailure(response: ApiResponse<T>)
override fun onResponse(call: Call<ApiResponse<T>>, response: Response<ApiResponse<T>>) {
if (response.isSuccessful && response.body() != null && response.code() == 200) {
onSuccess(response.body()!!)
} else { // handle 4xx & 5xx error codes here
val resp = ApiResponse<T>()
resp.status = false
resp.message = response.message()
onFailure(resp)
}
}
override fun onFailure(call: Call<ApiResponse<T>>, t: Throwable) {
val response = ApiResponse<T>()
response.status = false
response.message = t.message.toString()
onFailure(response)
}
}
Now use the above ApiCallback class instead of Retrofit's Callback class to enqueue
2. Want to cancel enqueue requests and also stop processing response if request is already sent in case of logout (because response parsing will modify some global data of app)
You cannot stop processing a response midway, but what you can do is not update the ui or activity if the activity in question is not in foreground, which can be done with the help of LiveData from the MVVM Architecture.
3. If access token has expired and 401 response received from cloud, it should get new token and then call API again automatically with new token.
Create a TokenAuthenticator.java class like this
public class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
}
Attach an instance of the above authenticator to OkHttpClient like this
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Then finally, attach okHttpClient to the Retrofit instance as you have already done
More info regarding the authenticator part can be found in this answer here

How to get the request URL for a Retrofit object?

I need to log the request URL that Retrofit creates. I don't find any getter methods on Retrofit object or web interface that is generated via Retrofit. The following is my code, where I want to log the address of every request:
public void onRequestFoods() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Const.BASE_LOCAL)
.addConverterFactory(GsonConverterFactory.create())
.build();
FoodOrderInterface foodInterface = retrofit.create(FoodOrderInterface.class);
Log.d(TAG, "onRequestFoods: request url: ");
foodInterface.listFoods().enqueue(new Callback<FoodResponse>() {
#Override
public void onResponse(Call<FoodResponse> call, Response<FoodResponse> response) {
List<Food> foods = response.body().getBody().getFoods();
mPresenter.onResponse((ArrayList<Food>) foods);
}
#Override
public void onFailure(Call<FoodResponse> call, Throwable t) {
mPresenter.onRequestFailed(t.getMessage());
}
});
}
I think what you need is http logging interceptor the github repo has a straightforward example of how to get it up and running

MockWebServer catch errors

I'm using RxJava, Retrofit and MockWebServer I'm doing unit tests on my services.
My restAdapter have a custom error handler returning a custom throwable depending on the error code :
RestAdapter restAdapter = new RestAdapter.Builder().setErrorHandler(new RetrofitErrorHandler(context))
I tried to use a TestSubscriber and to subscribe like this:
TestSubscriber<X> testSubscriber = new TestSubscriber<>();
Observable<X> observable = mService.myCall(null, email);
observable.subscribe(testSubscriber);
but the .getOnErrorEvents() returns 0 event.
What should I do ?
Retrofit returns as a successful onNext event any kind of response, even if the status code differs from 2XX.
I propose to map the response and manually throw an error if the response.isSuccess() method returns false.
public Observable<UserEntity> loginUser(UserEntity user, String password) {
return this.restApi.doLogin(user.getEmail(), password)
.map(new Func1<Response<UserEntity>, UserEntity>() {
#Override
public UserEntity call(Response<UserEntity> userEntityResponse) {
if (!userEntityResponse.isSuccess()) throw new RuntimeException();
return userEntityResponse.body();
}
});
}

How to handle the `IllegalArgumentException` in RxJava?

I'm using Retrofit with RxJava for the network calls and RxBinding for view operations. In signup screen, upon clicking 'Register' button I'm posting the info to the local server using the MyApi service.
SignupActivity.class
mCompositeSubscription.add(RxView.clicks(mRegisterButton).debounce(300, TimeUnit.MILLISECONDS).
subscribe(view -> {
registerUser();
}, e -> {
Timber.e(e, "RxView ");
onRegistrationFailed(e.getMessage());
}));
private void registerUser() {
mCompositeSubscription.add(api.registerUser(mEmail,
mPassword, mConfirmPassword)
.subscribe(user -> {
Timber.d("Received user object. Id: " + user.getUserId());
}, e -> {
Timber.e(e, "registerUser() ");
onRegistrationFailed(e.getMessage());
}));
}
MyApi.class
public Observable<User> registerUser(String username, String password, String confirmPassword) {
return mService.registerUser(username, password, confirmPassword)
.compose(applySchedulers());
}
#SuppressWarnings("unchecked") <T> Observable.Transformer<T, T> applySchedulers() {
return observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
MyService.class
#FormUrlEncoded #POST("users/")
Observable<User> registerUser(#Path("email") String username,
#Path("password") String password, #Path("password_confirmation") String confirmPassword);
The call fails with IllegalArgumentException since I'm posting an invalid info.
What's my main issue is, upon IllegalArgumentException I thought RxJava would execute registerUser()#ErrorHandler() since my registerUser service call failed with exception but instead it calls RxView#ErrorHandler().
How can I make/force registerUser()#ErrorHandler() to take care of the exception occurred during the network call?
My bad, the network call doesn't fail with IllegalArgumentException but the request construction itself failed with IllegalArgumentException.
java.lang.IllegalArgumentException: URL "users/" does not contain {email}".
Instead of using #Field annotation for constructing the POST body , I'd mistakenly used #Path annotation.
The correct definition:
#FormUrlEncoded #POST("users/")
Observable<User> registerUser(#Field("email") String username,
#Field("password") String password, #Field("password_confirmation") String confirmPassword);

How can I handle empty response body with Retrofit 2?

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) { }

Categories

Resources