I'm very new to android development. Trying to connect some site and get data from it. I have this function called only from onCreate in the main activity. Every time I turn virtual Android phone left or right I see new "run()" strings in EditText and requests in Wireshark. How to stop that properly?
Tried call.cancel() and mClient.dispatcher().cancelAll() inside OnResponse
protected void Load(String url) {
Request request = new Request.Builder()
.url(url)
.build();
mClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
});
}
retrofit supports enqueue canceling, and it works great.
And i think if you will try to run this code - your client enqueues would be stoped
protected void Load(String url) {
Request request = new Request.Builder()
.url(url)
.build();
Call<Response> mCall = mClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
if (call.isCanceled()) {
Log.e(TAG, "request was cancelled");
}
else {
Log.e(TAG, "other larger issue, i.e. no network connection?");
}
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
});
mCall.cancel();
}
I don't know you project structure and what kind of patterns you using(MVP, MVVM or else), but in simple, this code can be improved by returning Call
protected void Load(String url): Call<Response>
And then you can hadle you request status, and if it longer than 5 seconds for example, you call call.cancel() and request is stopping.
onCreate is called every time configuration changes (for example you rotate your phone/emulator). For more info: https://developer.android.com/guide/components/activities/activity-lifecycle
You can save your response to prevent new request on every onCreate. Something like this:
MainActivity {
private Response savedResponse;
onCreate() {
if (savedResponse == null) {
Load(url)
}
}
}
and in your onResponse save the response:
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
savedResponse = response; // <----
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
However, correct way would be to separete network calls/requests from activity lifecycle and load data somewhere else (Service, WorkManager, ...)
I want to take jsonobject using retrofit without class definition, the result is null. How about this ?
I have a get query with JSON response :
[{"Kuota":"12"}]
This my code get data JSONObject .
public void GetKuota(String Key) {
IBookingService iBookingService = APIClient.getClient().create(IBookingService.class);
Call<ResponseBody> call = iBookingService.getKuota(Key);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(!response.isSuccessful()) {
JSONObject jsonObject;
try {
jsonObject = new JSONObject(response.body().toString());
// Log.d(jsonObject.getString("Kuota"));
datas =String.valueOf(jsonObject.getInt("Kuota"));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
Suggestion:
Create a model class, lets say Kuota
public class Kuota {
public int Kuota;
}
Then rewrite the Retrofit callback like this.
iBookingService.getKuota(Key).enqueue(new Callback<List<Kuota>>() {
#Override
public void onResponse(Call<List<Kuota>> call, Response<List<Kuota>> response) {
if(response.code() == 200 && response.body() != null) {
try {
for(Kuota k: response.body()) {
Log.d("Kuota Value" , " " + k.kuota);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<List<Kuota>> call, Throwable t) {
}
});
Replace iBookingService data holder from ResponseBody to List< Kuota >
if not added, please add the JSON serializer while setting up Retrofit instance.
new Retrofit.Builder()
.......
.addConverterFactory(GsonConverterFactory.create())
.build();
And also add the dependency
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
I tried to post raw data to server using retrofit method, but I receive the failure response like res fail: Response{protocol=http/1.1, code=500, message=Internal Server Error, url=http://192.168.1.9:8000/api/deviceinfo}
apiService = RetrofitSingleton.createService(ApiService.class);
spyApp = new SpyApp();
Call<ModelResponse> call = apiService.eventsUpdate(jsonObj);
call.enqueue(new Callback<ModelResponse>() {
#Override
public void onResponse(Call<ModelResponse> call, Response<ModelResponse> response) {
Log.e("response", new Gson().toJson(response.body())); //This shows null
if (response.isSuccessful()) {
String status = response.body().getData().getStatus();
} else {
Log.e("res fail", response.toString());
}
}
#Override
public void onFailure(Call<ModelResponse> call, Throwable t) {
Log.e("failure",t.getMessage());
}
});
So when I make a POST API call to my server, I get a 400 Bad Request error with JSON response.
{
"userMessage": "Blah",
"internalMessage": "Bad Request blah blah",
"errorCode": 1
}
I call it by
Call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//AA
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//BB
}
}
However the problem is that once I get the response, onFailure() is invoke so that //BB is called. Here, I have no way to access the JSON response.
When I log the api request and response, it doesn't show JSON response at all. And Throwable t is IOException. However, strangely, when I make the same call on Postman, it does return the expected JSON response with 400 error code.
So my question is how can I get the json response when I get 400 Bad Request error? Should I add something to okhttpclient?
Thanks
You can do it in your onResponse method, remember 400 is a response status not an error:
if (response.code() == 400) {
Log.v("Error code 400",response.errorBody().string());
}
And you can handle any response code except 200-300 with Gson like that:
if (response.code() == 400) {
Gson gson = new GsonBuilder().create();
ErrorPojoClass mError=new ErrorPojoClass();
try {
mError= gson.fromJson(response.errorBody().string(),ErrorPojoClass.class);
Toast.makeText(context, mError.getDescription(), Toast.LENGTH_LONG).show();
} catch (IOException e) {
// handle failure to read error
}
}
Add this to your build.gradle : compile 'com.google.code.gson:gson:2.7'
If you want create Pojo class go to Json Schema 2 Pojo and paste your example Json response. Select source type Json and annotation Gson .
You can try the below code to get 400 response. You can get error response from errorBody() method.
Call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//get success and error response here
if (response.code() == 400) {
if(!response.isSuccessful()) {
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(response.errorBody().string());
String userMessage = jsonObject.getString("userMessage");
String internalMessage = jsonObject.getString("internalMessage");
String errorCode = jsonObject.getString("errorCode");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//get failure response here
}
}
}
EDIT: Fixed method name from toString to string
Handle ErrorResponse with your class object
Kotlin
val errorResponse = Gson().fromJson(response.errorBody()!!.charStream(), ErrorResponse::class.java)
Java
ErrorResponse errorResponse = new Gson().fromJson(response.errorBody.charStream(),ErrorResponse.class)
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
DialogHelper.dismiss();
if (response.isSuccessful()) {
// Success
} else {
try {
JSONObject jObjError = new JSONObject(response.errorBody().string());
Toast.makeText(getContext(), jObjError.getString("message"), Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
First step:
Create your POJO class for error response. In my case, ApiError.java
public class ApiError {
#SerializedName("errorMessage")
#Expose
private String errorMessage;
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage= errorMessage;
}
}
Second Step:
Write below code in your api callback.
Call.enqueue(new Callback<RegistrationResponse>() {
#Override
public void onResponse(Call<RegistrationResponse> call, Response<RegistrationResponse> response)
{
if (response.isSuccessful()) {
// do your code here
} else if (response.code() == 400) {
Converter<ResponseBody, ApiError> converter =
ApiClient.retrofit.responseBodyConverter(ApiError.class, new Annotation[0]);
ApiError error;
try {
error = converter.convert(response.errorBody());
Log.e("error message", error.getErrorMessage());
Toast.makeText(context, error.getErrorMessage(), Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<RegistrationResponse> call, Throwable t) {
//do your failure handling code here
}
}
Here ApiClient.retrofit is your retrofit instance which is static.
I got similar issue, but existing code was stick to RxJava 2 chain.
Here's my solution:
public static <T> Observable<T> rxified(final Call<T> request, final Class<T> klazz) {
return Observable.create(new ObservableOnSubscribe<T>() {
AtomicBoolean justDisposed = new AtomicBoolean(false);
#Override
public void subscribe(final ObservableEmitter<T> emitter) throws Exception {
emitter.setDisposable(new Disposable() {
#Override
public void dispose() {
request.cancel();
justDisposed.set(true);
}
#Override
public boolean isDisposed() {
return justDisposed.get();
}
});
if (!emitter.isDisposed())
request.enqueue(new Callback<T>() {
#Override
public void onResponse(Call<T> call, retrofit2.Response<T> response) {
if (!emitter.isDisposed()) {
if (response.isSuccessful()) {
emitter.onNext(response.body());
emitter.onComplete();
} else {
Gson gson = new Gson();
try {
T errorResponse = gson.fromJson(response.errorBody().string(), klazz);
emitter.onNext(errorResponse);
emitter.onComplete();
} catch (IOException e) {
emitter.onError(e);
}
}
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
if (!emitter.isDisposed()) emitter.onError(t);
}
});
}
});
}
transforming 400-like responses into rx chain is pretty simple:
Call<Cat> request = catApi.getCat();
rxified(request, Cat.class).subscribe( (cat) -> println(cat) );
Here is the simplest solution,
If you want to handle the response from onFailure method:
#Override
public void onFailure(Call<T> call, Throwable t) {
HttpException httpException = (HttpException) t;
String errorBody = httpException.response().errorBody().string();
// use Gson to parse json to your Error handling model class
ErrorResponse errorResponse = Gson().fromJson(errorBody, ErrorResponse.class);
}
Or if you are using rxjava Observable with Kotlin, handle it from error body:
{ error ->
val httpException :HttpException = error as HttpException
val errorBody: String = httpException.response().errorBody()!!.string()
// use Gson to parse json to your Error handling model class
val errorResponse: ErrorResponse =
Gson().fromJson(errorBody, ErrorResponse::class.java)
}
Don't forget to properly handle json to class conversion (use try-catch if not sure).
simply use
if (throwable is HttpException && (throwable!!.code() == 400 || throwable!!.code()==404)){
var responseBody = throwable!!.response()?.errorBody()?.string()
val jsonObject = JSONObject(responseBody!!.trim())
var message = jsonObject.getString("message")
tvValMsg.set(message)
}
This is how you can handle the response message
I am handling for error 500 you can add as much you want
switch (response.code()) {
case HttpURLConnection.HTTP_OK:
break;
case HttpURLConnection.HTTP_UNAUTHORIZED:
callback.onUnAuthentic();
break;
case HttpURLConnection.HTTP_INTERNAL_ERROR:
try {
String errorResponse = response.errorBody().string();
JSONObject object = new JSONObject(errorResponse);
String message = "Error";
if (object.has("Message"))
message = String.valueOf(object.get("Message"));
callback.onError(message);
} catch (IOException e) {
e.printStackTrace();
}
break;
case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:
case HttpURLConnection.HTTP_CLIENT_TIMEOUT:
default:
callback.onNetworkError();
break;
}
IF you are getting 400(Bad Request) by using retrofit first make sure are are setting input to API is Only Model class, If not then replace input request by Model class and then check you will get Success response.
#POST("api/users/CreateAccount")
Call<CreateAccount> createAccount(#Body CreateAccount model, #Header("Content-Type") String content_type);
I want to do an unit test that verifies if function1() or function2() were called. I haven't work with callbacks before, can you give me any idea about how to do it?
public void sendData(HttpService service, Document userData) {
Call<String> call = service.updateDocument(getId(), userData);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
function1(response.code());
}
#Override
public void onFailure(Call<String> call, Throwable t) {
function2();
}
});
}
I couldn't try, but it should work. Maybe you have to fix generic type
casting errors like mock(Call.class);.
#Test
public void should_test_on_response(){
Call<String> onResponseCall = mock(Call.class);
doAnswer(invocation -> {
Response response = null;
invocation.getArgumentAt(0, Callback.class).onResponse(onResponseCall, response);
return null;
}).when(onResponseCall).enqueue(any(Callback.class));
sendData(....);
// verify function1
}
#Test
public void should_test_on_failure(){
Call<String> onResponseCall = mock(Call.class);
doAnswer(invocation -> {
Exception ex = new RuntimeException();
invocation.getArgumentAt(0, Callback.class).onFailure(onResponseCall, ex);
return null;
}).when(onResponseCall).enqueue(any(Callback.class));
sendData(....);
// verify function2
}