How can I return String or JSONObject from asynchronous callback using Retrofit?

后端 未结 7 1785
无人及你
无人及你 2020-11-30 00:34

For example, calling

api.getUserName(userId, new Callback() {...});

cause:

retrofit.RetrofitError: retrofit.         


        
相关标签:
7条回答
  • 2020-11-30 00:51

    When @lordmegamax answer completely work there is much nicer solution which is come from

    Okio is a new library that complements java.io and java.nio

    other squares project which already tight with retrofit and therefore you don't need to add any new dependency and it's have to be reliable:

    ByteString.read(body.in(), (int) body.length()).utf8();
    

    ByteString is an immutable sequence of bytes. For character data, String is fundamental. ByteString is String's long-lost brother, making it easy to treat binary data as a value. This class is ergonomic: it knows how to encode and decode itself as hex, base64, and UTF-8.

    Full example:

    public class StringConverter implements Converter {
      @Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
        try {
          return ByteString.read(body.in(), (int) body.length()).utf8();
        } catch (IOException e) {
          throw new ConversionException("Problem when convert string", e);
        }
      }
    
      @Override public TypedOutput toBody(Object object) {
        return new TypedString((String) object);
      }
    }
    
    0 讨论(0)
  • 2020-11-30 00:54

    Retrofit 2.0.0-beta3 adds a converter-scalars module provides a Converter.Factory for converting String, the 8 primitive types, and the 8 boxed primitive types as text/plain bodies. Install this before your normal converter to avoid passing these simple scalars through, for example, a JSON converter.

    So, first add converter-scalars module to build.gradle file for your application.

    dependencies {
        ...
        // use your Retrofit version (requires at minimum 2.0.0-beta3) instead of 2.0.0
        // also do not forget to add other Retrofit module you needed
        compile 'com.squareup.retrofit2:converter-scalars:2.0.0'
    }
    

    Then, create your Retrofit instance like this:

    new Retrofit.Builder()
            .baseUrl(BASE_URL)
            // add the converter-scalars for coverting String
            .addConverterFactory(ScalarsConverterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build()
            .create(Service.class);
    

    Now you can use API declaration like this:

    interface Service {
    
        @GET("/users/{id}/name")
        Call<String> userName(@Path("userId") String userId);
    
        // RxJava version
        @GET("/users/{id}/name")
        Observable<String> userName(@Path("userId") String userId);
    }
    
    0 讨论(0)
  • 2020-11-30 00:59

    The answer may be much shorter than already mentioned and doesn't require any additional libraries:

    In declaration use Response as follows:

    ... Callback<Response> callback);
    

    And while handling response:

    @Override
    public void success(Response s, Response response) {
        new JSONObject(new String(((TypedByteArray) response.getBody()).getBytes()))
    }
    
    0 讨论(0)
  • 2020-11-30 01:05

    I figured it out. It's embarrassing but it was very simple... Temporary solution may be like this:

     public void success(Response response, Response ignored) {
                TypedInput body = response.getBody();
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(body.in()));
                    StringBuilder out = new StringBuilder();
                    String newLine = System.getProperty("line.separator");
                    String line;
                    while ((line = reader.readLine()) != null) {
                        out.append(line);
                        out.append(newLine);
                    }
    
                    // Prints the correct String representation of body. 
                    System.out.println(out);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    

    But if you want to get directly Callback the Better way is to use Converter.

    public class Main {
    public interface ApiService {
        @GET("/api/")
        public void getJson(Callback<String> callback);
    }
    
    public static void main(String[] args) {
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setClient(new MockClient())
                .setConverter(new StringConverter())
                .setEndpoint("http://www.example.com").build();
    
        ApiService service = restAdapter.create(ApiService.class);
        service.getJson(new Callback<String>() {
            @Override
            public void success(String str, Response ignored) {
                // Prints the correct String representation of body.
                System.out.println(str);
            }
    
            @Override
            public void failure(RetrofitError retrofitError) {
                System.out.println("Failure, retrofitError" + retrofitError);
            }
        });
    }
    
    static class StringConverter implements Converter {
    
        @Override
        public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
            String text = null;
            try {
                text = fromStream(typedInput.in());
            } catch (IOException ignored) {/*NOP*/ }
    
            return text;
        }
    
        @Override
        public TypedOutput toBody(Object o) {
            return null;
        }
    
        public static String fromStream(InputStream in) throws IOException {
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            StringBuilder out = new StringBuilder();
            String newLine = System.getProperty("line.separator");
            String line;
            while ((line = reader.readLine()) != null) {
                out.append(line);
                out.append(newLine);
            }
            return out.toString();
        }
    }
    
    public static class MockClient implements Client {
        @Override
        public Response execute(Request request) throws IOException {
            URI uri = URI.create(request.getUrl());
            String responseString = "";
    
            if (uri.getPath().equals("/api/")) {
                responseString = "{result:\"ok\"}";
            } else {
                responseString = "{result:\"error\"}";
            }
    
            return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST,
                    new TypedByteArray("application/json", responseString.getBytes()));
        }
      }
    }
    

    If you know how to improve this code - please feel free to write about it.

    0 讨论(0)
  • 2020-11-30 01:05

    A possible solution would be to use JsonElement as the Callback type (Callback<JsonElement>). In your original example:

    api.getUserName(userId, new Callback<JsonElement>() {...});
    

    In the success method you can convert the JsonElement to either a String or a JsonObject.

    JsonObject jsonObj = element.getAsJsonObject();
    String strObj = element.toString();
    
    0 讨论(0)
  • 2020-11-30 01:16

    Here is what I did, after poking around in the debugger. Note: this is for actually getting it inside an error callback, not the success callback.

    You'll see that the success type is found by calling retrofitError.getSuccessType() and returns and object of type Type

    You can then call retrofitError.getBodyAs(YourType.class) which is all I needed to do because for me its always the class I expect it to be.

    Here is the one-liner answer:

    retrofitError.getBodyAs(retrofitError.getSuccessType())
    

    Now, I'll note that I dont have to do anything like this regarding the success callback because it's already working magically.

    0 讨论(0)
提交回复
热议问题