Ich benutze Retrofit 2.0.0-beta1 .
In Tests habe ich ein alternatives Szenario und erwarte den Fehler HTTP 400
Ich hätte gerne retrofit.Response<MyError> response
Aber response.body() == null
MyError wird nicht deserialisiert - ich sehe es nur hier
response.errorBody().string()
aber es gibt mir nicht MyError als Objekt
Ich verwende derzeit eine sehr einfache Implementierung, für die keine Konverter oder spezielle Klassen verwendet werden müssen. Der Code, den ich verwende, ist folgender:
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
DialogHelper.dismiss();
if (response.isSuccessful()) {
// Do your success stuff...
} 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();
}
}
}
In Retrofit 2.0 Beta2 erhalte ich auf diese Weise Fehlerantworten:
Synchron
try {
Call<RegistrationResponse> call = backendServiceApi.register(data.in.account, data.in.password,
data.in.email);
Response<RegistrationResponse> response = call.execute();
if (response != null && !response.isSuccess() && response.errorBody() != null) {
Converter<ResponseBody, BasicResponse> errorConverter =
MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
BasicResponse error = errorConverter.convert(response.errorBody());
//DO ERROR HANDLING HERE
return;
}
RegistrationResponse registrationResponse = response.body();
//DO SUCCESS HANDLING HERE
} catch (IOException e) {
//DO NETWORK ERROR HANDLING HERE
}
Asynchron
Call<BasicResponse> call = service.loadRepo();
call.enqueue(new Callback<BasicResponse>() {
@Override
public void onResponse(Response<BasicResponse> response, Retrofit retrofit) {
if (response != null && !response.isSuccess() && response.errorBody() != null) {
Converter<ResponseBody, BasicResponse> errorConverter =
retrofit.responseConverter(BasicResponse.class, new Annotation[0]);
BasicResponse error = errorConverter.convert(response.errorBody());
//DO ERROR HANDLING HERE
return;
}
RegistrationResponse registrationResponse = response.body();
//DO SUCCESS HANDLING HERE
}
@Override
public void onFailure(Throwable t) {
//DO NETWORK ERROR HANDLING HERE
}
});
Update für Retrofit 2 beta3:
Asynchron - Der Retrofit-Parameter wurde aus onResponse entfernt
Call<BasicResponse> call = service.loadRepo();
call.enqueue(new Callback<BasicResponse>() {
@Override
public void onResponse(Response<BasicResponse> response) {
if (response != null && !response.isSuccess() && response.errorBody() != null) {
Converter<ResponseBody, BasicResponse> errorConverter =
MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
BasicResponse error = errorConverter.convert(response.errorBody());
//DO ERROR HANDLING HERE
return;
}
RegistrationResponse registrationResponse = response.body();
//DO SUCCESS HANDLING HERE
}
@Override
public void onFailure(Throwable t) {
//DO NETWORK ERROR HANDLING HERE
}
});
Ich habe es gelöst durch:
if(!response.isSuccessful()){
Gson gson = new Gson();
MyErrorMessage message=gson.fromJson(response.errorBody().charStream(),MyErrorMessage.class);
if(message.getCode()==ErrorCode.DUPLICATE_EMAIL_ID_CODE){
//DO Error Code specific handling
}else{
//DO GENERAL Error Code Specific handling
}
}
MyErrorMessage-Klasse:
public class MyErrorMessage {
private int code;
private String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
In https://stackoverflow.com/a/21103420/2914140 und https://futurestud.io/tutorials/retrofit-2-simple-error-handling Diese Variante wird für Retrofit 2.1 angezeigt. 0.
call.enqueue(new Callback<MyResponse>() {
@Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
if (response.isSuccessful()) {
...
} else {
Converter<ResponseBody, MyError> converter
= MyApplication.getRetrofit().responseBodyConverter(
MyError.class, new Annotation[0]);
MyError errorResponse = null;
try {
errorResponse = converter.convert(response.errorBody());
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ich habe es so für asynchrone Aufrufe mit Retrofit 2.0-beta2 gemacht:
@Override
public void onResponse(Response<RegistrationResponse> response,
Retrofit retrofit) {
if (response.isSuccess()) {
// Do success handling here
} else {
try {
MyError myError = (MyError)retrofit.responseConverter(
MyError.class, MyError.class.getAnnotations())
.convert(response.errorBody());
// Do error handling here
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onResponse(Call<Void> call, retrofit2.Response<Void> response) {
if (response.isSuccessful()) {
//Do something if response is ok
} else {
JsonParser parser = new JsonParser();
JsonElement mJson = null;
try {
mJson = parser.parse(response.errorBody().string());
Gson gson = new Gson();
MyError errorResponse = gson.fromJson(mJson, MyError.class);
} catch (IOException ex) {
ex.printStackTrace();
}
}
ErrorResponse ist Ihr benutzerdefiniertes Antwortobjekt
Kotlin
val gson = Gson()
val type = object : TypeToken<ErrorResponse>() {}.type
var errorResponse: ErrorResponse? = gson.fromJson(response.errorBody()!!.charStream(), type)
Java
Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse errorResponse = gson.fromJson(response.errorBody.charStream(),type);
Auf diese Weise benötigen Sie keine Retrofit-Instanz, wenn Sie nur einen aus Retrofit erstellten Dienst einschleusen.
public class ErrorUtils {
public static APIError parseError(Context context, Response<?> response) {
APIError error = new APIError();
try {
Gson gson = new Gson();
error = gson.fromJson(response.errorBody().charStream(), APIError.class);
} catch (Exception e) {
Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
}
if (TextUtils.isEmpty(error.getErrorMessage())) {
error.setError(response.raw().message());
}
return error;
}
}
Verwenden Sie es so:
if (response.isSuccessful()) {
...
} else {
String msg = ErrorUtils.parseError(fragment.getActivity(), response).getError(); // would be from your error class
Snackbar.make(someview, msg, Snackbar.LENGTH_LONG).show();
}
}
Dies scheint das Problem zu sein, wenn Sie OkHttp zusammen mit Retrofit verwenden. Sie können also entweder OkHttp entfernen oder den folgenden Code verwenden, um den Fehlerrumpf abzurufen:
if (!response.isSuccessful()) {
InputStream i = response.errorBody().byteStream();
BufferedReader r = new BufferedReader(new InputStreamReader(i));
StringBuilder errorResult = new StringBuilder();
String line;
try {
while ((line = r.readLine()) != null) {
errorResult.append(line).append('\n');
}
} catch (IOException e) {
e.printStackTrace();
}
}
Ich war mit demselben Problem konfrontiert. Ich habe es mit Retrofit gelöst. Lass mich das zeigen ...
Wenn deine Fehler JSON-Struktur sind
{
"error": {
"status": "The email field is required."
}
}
My ErrorRespnce.Java
public class ErrorResponce {
@SerializedName("error")
@Expose
private ErrorStatus error;
public ErrorStatus getError() {
return error;
}
public void setError(ErrorStatus error) {
this.error = error;
}
}
Und dies ist meine Fehlerstatusklasse
public class ErrorStatus {
@SerializedName("status")
@Expose
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
Jetzt brauchen wir eine Klasse, die mit unserem Json umgehen kann.
public class ErrorUtils {
public static ErrorResponce parseError (Response<?> response){
Converter<ResponseBody , ErrorResponce> converter = ApiClient.getClient().responseBodyConverter(ErrorResponce.class , new Annotation[0]);
ErrorResponce errorResponce;
try{
errorResponce = converter.convert(response.errorBody());
}catch (IOException e){
return new ErrorResponce();
}
return errorResponce;
}
}
Jetzt können wir unsere Antwort im nachgerüsteten api-Aufruf überprüfen
private void registrationRequest(String name , String email , String password , String c_password){
final Call<RegistrationResponce> registrationResponceCall = apiInterface.getRegistration(name , email , password , c_password);
registrationResponceCall.enqueue(new Callback<RegistrationResponce>() {
@Override
public void onResponse(Call<RegistrationResponce> call, Response<RegistrationResponce> response) {
if (response.code() == 200){
}else if (response.code() == 401){
ErrorResponce errorResponce = ErrorUtils.parseError(response);
Toast.makeText(MainActivity.this, ""+errorResponce.getError().getStatus(), Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<RegistrationResponce> call, Throwable t) {
}
});
}
Jetzt können Sie Ihren Toast zeigen
Wenn Sie Kotlin verwenden, kann eine andere Lösung darin bestehen, eine Erweiterungsfunktion für die Antwortklasse zu erstellen:
inline fun <reified T>Response<*>.parseErrJsonResponse(): T?
{
val moshi = MyCustomMoshiBuilder().build()
val parser = moshi.adapter(T::class.Java)
val response = errorBody()?.string()
if(response != null)
try {
return parser.fromJson(response)
} catch(e: JsonDataException) {
e.printStackTrace()
}
return null
}
Verwendungszweck
val myError = response.parseErrJsonResponse<MyErrorResponse>()
if(myError != null) {
// handle your error logic here
// ...
}
Wie wäre es mit einer verallgemeinerten Lösung? Ich denke, so etwas kann funktionieren:
abstract class TestCallback<RESPONSE, ERROR extends Throwable> implements Callback<RESPONSE> {
Class<ERROR> errorClass;
Retrofit retrofit;
TestCallback(Retrofit retrofit, Class<ERROR> errorClass) {
this.retrofit = retrofit;
this.errorClass = errorClass;
}
abstract void onSuccess(Call<RESPONSE> call, RESPONSE response);
@Override
public void onResponse(Call<RESPONSE> call, Response<RESPONSE> response) {
if (response.isSuccessful()) {
onSuccess(call, response.body());
return;
}
if (response.errorBody() != null) {
Converter<ResponseBody, ERROR> converter = retrofit.responseBodyConverter(errorClass, new Annotation[0]);
ERROR error;
try {
error = converter.convert(response.errorBody());
onFailure(call, error);
} catch (IOException e) {
// Conversion error. Add some meaningful message or return a custom error.
onFailure(call, new Throwable());
}
} else {
// Unknown HTTP error (errorBody == null). Add some meaningful message or return a custom error.
onFailure(call, new Throwable());
}
}
}
Und dann können wir diese benutzerdefinierte Callback<>
auf folgende Weise verwenden:
Call<User> call = retrofit.create(UsersApi.class).signUp(email, password);
call.enqueue(new TestCallback<User, TestError>(retrofit, TestError.class) {
@Override
public void onFailure(Call<User> call, Throwable t) {
if (t instanceof TestError) {
} else {
}
}
@Override
void onSuccess(Call<User> call, User response) {
// No need to check for isSuccessful() + no need to
// duplicate the same code for all of your handlers.
}
});
Lesen Sie errorBody in einen String und analysieren Sie json manuell.
if(!response.isSuccessful()) {
response.errorBody();
String error = "";
try {
BufferedReader ereader = new BufferedReader(new InputStreamReader(
response.errorBody().byteStream()));
String eline = null;
while ((eline = ereader.readLine()) != null) {
error += eline + "";
}
ereader.close();
} catch (Exception e) {
error += e.getMessage();
}
Log.e("Error",error);
try {
JSONObject reader = new JSONObject(error);
String message = reader.getString("message");
Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
} catch (JSONException e) {
e.printStackTrace();
}
}
try{
ResponseBody response = ((HttpException) t).response().errorBody();
JSONObject json = new JSONObject( new String(response.bytes()) );
errMsg = json.getString("message");
}catch(JSONException e){
return t.getMessage();
}
catch(IOException e){
return t.getMessage();
}
errorBody-Werte sollten das APIError-Objekt in Retrofit festlegen. So können Sie die folgende Codestruktur verwenden.
öffentliche Klasse APIErrorUtils {
public static APIError parseError(Response<?> response) {
Converter<ResponseBody, APIError> converter = API.getClient().responseBodyConverter(APIError.class, new Annotation[0]);
APIError error;
try {
error = converter.convert(response.errorBody());
Log.d("SERVICELOG", "****************************************************");
Log.d("SERVICELOG", "***** SERVICE LOG");
Log.d("SERVICELOG", "***** TIMESTAMP: " + String.valueOf(error.getTimestamp()));
Log.d("SERVICELOG", "***** STATUS: " + String.valueOf(error.getStatus()));
Log.d("SERVICELOG", "***** ERROR: " + error.getError());
Log.d("SERVICELOG", "***** MESSAGE: " + error.getMessage());
Log.d("SERVICELOG", "***** PATH: " + error.getPath());
Log.d("SERVICELOG", "****************************************************");
} catch (IOException e) {
return new APIError();
}
return error;
}
}
APIError error = APIErrorUtils.parseError (Antwort); If (error.getStatus () == 400) { ....}
gelöst durch:
Converter<MyError> converter =
(Converter<MyError>)JacksonConverterFactory.create().get(MyError.class);
MyError myError = converter.fromBody(response.errorBody());
In Kotlin:
val call = APIClient.getInstance().signIn(AuthRequestWrapper(AuthRequest("1234567890z", "12341234", "nonce")))
call.enqueue(object : Callback<AuthResponse> {
override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
if (response.isSuccessful) {
} else {
val a = object : Annotation{}
val errorConverter = RentalGeekClient.getRetrofitInstance().responseBodyConverter<AuthFailureResponse>(AuthFailureResponse::class.Java, arrayOf(a))
val authFailureResponse = errorConverter.convert(response.errorBody())
}
}
override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
}
})