Multiple converters with Retrofit 2

12,655

You need to return null from the Converter.Factory if the type does not match. Keep the Class<?> around in a field to compare it against.

@Override
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
  if (!this.type.equals(type)) {
    return null;
  }
  return new HALResponseBodyConverter<>(gson);
}

This will allow multiple instances to be used because each only applies to its own type.

That said, however, you can probably get away with only using a single converter and pulling the class from the Type that is passed in.

@Override
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
  if (!HALResponse.class.isAssignableFrom(type)) {
    return null;
  }
  // TODO create converter with `type` now that you know what it is...
}

You can look at the Wire converter in the repo which does this for a full example.

Share:
12,655

Related videos on Youtube

Gabor
Author by

Gabor

Updated on June 15, 2022

Comments

  • Gabor
    Gabor about 2 years

    I have a HATEOAS (HAL) REST service and managed to talk to it with the code below (using halarious as a conversion engine) but when I try to merge the converters (stallone and stallone2), the app will always pick up the first converter, instead of the one that is appropriate for the response type which of course leads to an error.

    How could I avoid duplicate retrofits that are only different in a small type detail?

    public interface Stallone {
       @GET("/discovery")
       Call<DiscoveryResponse> discover();
       @POST()
       Call<LoginResponse> login(@Url String url, @Body LoginRequest secret);
    }
    
       public static void main(String... args) throws IOException {
          // Initialize a converter for each supported (return) type
          final Stallone stallone = new Retrofit.Builder()
             .baseUrl(BASE)
             .addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class))
             .build().create(Stallone.class);
          final Stallone stallone2 = new Retrofit.Builder()
             .baseUrl(BASE)
             .addConverterFactory(HALConverterFactory.create(LoginResponse.class))
             .build().create(Stallone.class);
    
          // Follow the HAL links
          Response<DiscoveryResponse> response = stallone.discover().execute();
          System.out.println(response.code() + " " + response.message());
          Assert.assertNotNull(response.body());
          String loginPath = response.body().getLogin();
          Assert.assertEquals(loginPath, "/login");
    
          // Follow another link
          if (loginPath.startsWith("/"))
             loginPath = loginPath.substring(1);
          Response<LoginResponse> response2 =
             stallone2.login(loginPath,
                            new LoginRequest(AUTH0TOKEN, null)).execute();
          System.out.println(response2.code() + " " + response2.message());
          Assert.assertNotNull(response2.body());
    
          String setupPath = response2.body().getSetup();
          Assert.assertEquals(setupPath, "/setup");
    
          System.out.println("All OK!");
       }
    
    public final class HALConverterFactory extends Converter.Factory {
    
       private final Gson gson;
    
       public static HALConverterFactory create(Class<?> type) {
          return new HALConverterFactory(type);
       }
    
       private HALConverterFactory(Class<?> type) {
          if (!HalResource.class.isAssignableFrom(type))
             throw new NullPointerException("Type should be a subclass of HalResource");
          GsonBuilder builder = new GsonBuilder();
          builder.registerTypeAdapter(HalResource.class, new HalSerializer());
          builder.registerTypeAdapter(HalResource.class, new HalDeserializer(type));
          builder.setExclusionStrategies(new HalExclusionStrategy());
          this.gson = builder.create();
       }
    
       @Override
       public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
          return new HALResponseBodyConverter<>(gson);
       }
    
       @Override public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) {
          return new GsonRequestBodyConverter<>(gson, type);
       }
    }
    
    final class HALResponseBodyConverter<T extends HalResource>
       implements Converter<ResponseBody, T> {
       private final Gson gson;
    
       HALResponseBodyConverter(Gson gson) {
          this.gson = gson;
       }
    
       @Override public T convert(ResponseBody value) throws IOException {
          BufferedSource source = value.source();
          try {
             String s = source.readString(Charset.forName("UTF-8"));
             return (T) gson.fromJson(s, HalResource.class);
          } catch (Exception e) {
             throw new RuntimeException(e);
          } finally {
             closeQuietly(source);
          }
       }
    
       private static void closeQuietly(Closeable closeable) {
          if (closeable == null) return;
          try {
             closeable.close();
          } catch (IOException ignored) {
          }
       }
    }
    

    Again, the problem is that when you try to shorten the above like this:

      final Stallone stallone = new Retrofit.Builder()
         .baseUrl(BASE)
    .addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class))
         .addConverterFactory(HALConverterFactory.create(LoginResponse.class))
         .build().create(Stallone.class);
    

    you'll get an exception at the Response<LoginResponse> response2 = ... line:

    Exception in thread "main" java.lang.ClassCastException: com.example.retrofit.DiscoveryResponse cannot be cast to com.example.retrofit.LoginResponse

    • naXa stands with Ukraine
      naXa stands with Ukraine over 7 years
      What's GsonRequestBodyConverter?
  • Shishir Shetty
    Shishir Shetty almost 5 years
    Where can I find EditUserXmlConverterFactory?
  • Shishir Shetty
    Shishir Shetty almost 5 years
    Also, where to write this: if (type != NeedToBeXML.class) return null; ?