【问题标题】:Convert retrofit callback value to return enveloped object将改造回调值转换为返回封装对象
【发布时间】:2018-04-03 17:27:34
【问题描述】:

我的问题是关于使用 RxJava for Android 来处理来自 Retrofit 调用的数据的可能性。

我刚刚开始使用这些库,所以如果我的问题很琐碎,请耐心等待。

这是我的场景。

我有一个从服务器返回的 json,看起来像这样

  { <--- ObjectResponse
     higher_level: {
        data: [
            {
               ...
               some fields,
               some inner object: {
               ....
                },
               other fields
            }, <----- obj 1

            ....,

            {
               ....
            }<---- obj n

         ]<-- array of SingleObjects

      }

  } <--- whole ObjectResponse

我已经改造得到这个响应并在 ObjectResponse 中解析。解析这个对象,我可以获得一个可以像往常一样传递给我的 RecyclerView 适配器的列表。

因此改造返回了 ObjectResponse,它是整个服务器答案的模型,并且在改造回调中我操纵 ObjectResponse 以提取我的列表,然后将其传递给我的适配器。

现在,我有这样的东西

Call<ObjectResponse> call = apiInterface.getMyWholeObject();

call.enqueue(new Callback<ObjectResponse>() {
            @Override
            public void onResponse(Call<ObjectResponse> call, Response<ObjectResponse> response) {

                //various manipulation based on response.body() that in the ends 
                // lead to a List<SingleObject>

               mView.sendToAdapter(listSingleObject)
              }

            @Override
            public void onFailure(Call<ObjectResponse> call, 
                  Throwable t) {
                t.printStackTrace();
            }
         });

我的问题是:

有没有办法从改造中获得一个 Observable,最终可以引导我发出 SingleObject 列表(并操纵它),而不必像在改造回调中那样操纵 ObjectResponse?或者我应该坚持使用改造回调,并且只有在获得列表之后我才能使用 RxJava 操作,然后再将此列表提供给我的适配器?

我想要这样的东西

        apiInterface
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<List<SingleObject>>() {
                @Override
                public void onCompleted() {

                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onNext(List<Post> posts) {

                  mView.sendToAdapter(listSingleObject)
                }
            });

【问题讨论】:

    标签: java android rx-java retrofit2 rx-android


    【解决方案1】:

    改造转换器工厂很好地解决了这个问题。

    Jake Wharton 在他的 "Making Retrofit Work For You" 演讲中谈到了 “信封” 对象,并指出了如何解决这个问题。

    已经定义了一个信封类 - 一个包含一些您不关心的额外字段的类:

    public class Envelope<T> {
        Meta meta;
        List<Notification> notifications;
        T response;
    }
    

    在这个 POJO 字段中,metaList&lt;Notification&gt; 正在从后端返回,但在 android 应用程序的上下文中,我们对它们并不感兴趣。假设您需要从响应中获取的真正值是名为 response 的字段,它可以是任何对象(因为它是通用的)。

    特别是在您的示例中,POJO 结构将是这样的:

    public class OriginalResponse {
        HigherLevel higher_level;
    }
    
    public class HigherLevel {
        Data data;
    }
    
    public class Data {
        List<ActualData> list;
    }
    

    你必须实现你的自定义Converter.Factory

    public class EnvelopingConverter extends Converter.Factory {
        @Nullable
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
            Type envelopedType = TypeToken.getParameterized(Envelope.class, type).getType();
    
            Converter<ResponseBody, Envelope<?>> delegate = retrofit.nextResponseBodyConverter(this, envelopedType, annotations);
            return body -> {
                Envelope<?> envelope = delegate.convert(body);
                // Here return the object that you actually are interested
                // in your example it would be:
                // originalResponse = delegate.convert(body);
                // return originalResponse.higher_level.data.list;
                return envelope.response;
            };
        }
    }
    

    将此转换器添加到您的改造制造商:

    new Retrofit.Builder() ... .addConverterFactory(new EnvelopingConverter()) ...

    然后在你的改造api接口中而不是直接返回Single&lt;OriginalResponse&gt;返回Single&lt;List&lt;ActualData&gt;&gt;

    interface Service {
        @GET(...)
        Single<List<ActualData>> getFoo();
    }
    

    Kotlin 中的典型实现:

    class EnvelopeConverterFactory : Converter.Factory() {
      override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? {
        val envelopedType: Type = TypeToken.getParameterized(ParentObject::class.java, type).type
    
        val delegate: Converter<ResponseBody, ParentObject> =
          retrofit.nextResponseBodyConverter(this, envelopedType, annotations)
    
        return Converter<ResponseBody, ChildObject> { body -> delegate.convert(body)?.childObject }
      }
    }
    

    【讨论】:

    • 抱歉我的回复晚了。由于 Lambda 表达式不适用于我的 Android Studio 安装,我似乎无法让你的解决方案发挥作用。我找到了 [link]f2prateek.com/2017/04/21/unwrapping-data-with-retrofit-2 这个解决方案,但我仍然在尝试让一切正常工作时遇到了一些麻烦。我想我会坚持使用其他解决方案,并在我有更多空闲时间时尝试您的解决方案。感谢您的努力。
    【解决方案2】:

    几天后,我可以发布自己的解决方案。

    它的灵感来自azizbekian 提出的想法。

    中心思想是关于 Envelope 类,我使用改造注释来表达它以确保它能够适应来自服务器的不同 JSON 响应,解析

    higher_level: {
            data: [
                    mid_level: {  .. },
                    ...
                  ]
    }
    

    我在原帖中已经解释过的结构

    public class WrapperResponse<T> {
        @SerializedName(value="higher_level", alternate={"mid_level", "other"})
        @Expose
        DataResponse<T> data;
    
        public DataResponse<T> getData() {
            return data;
        }
    
        public void setData(DataResponse<T> data) {
            this.data = data;
        }
    }
    

    这里的重点是 SerializedName 的参数,我在其中指定了出现在我的服务器响应中的所有可能的 JSON 对象名称。

    那我有

    public class UnwrapConverterFactory extends Converter.Factory {
    
        private GsonConverterFactory factory;
    
        public UnwrapConverterFactory(GsonConverterFactory factory) {
            this.factory = factory;
        }
    
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(final Type type,
                                                                Annotation[] annotations, Retrofit retrofit) {
            Type wrappedType = new ParameterizedType() {
                @Override
                public Type[] getActualTypeArguments() {
                    return new Type[] {type};
                }
    
                @Override
                public Type getOwnerType() {
                    return null;
                }
    
                @Override
                public Type getRawType() {
                    return WrapperResponse.class;
                }
            };
            Converter<ResponseBody, ?> gsonConverter = factory
                    .responseBodyConverter(wrappedType, annotations, retrofit);
            return new WrapperResponseBodyConverter(gsonConverter);
        }
    }
    

    public class WrapperResponseBodyConverter<T>
            implements Converter<ResponseBody, T> {
        private Converter<ResponseBody, WrapperResponse<T>> converter;
    
        public WrapperResponseBodyConverter(Converter<ResponseBody,
                WrapperResponse<T>> converter) {
            this.converter = converter;
        }
    
        @Override
        public T convert(ResponseBody value) throws IOException {
            WrapperResponse<T> response = converter.convert(value);
    
                return response.getData().getData();
    
        }
    }
    

    在我的改造模块 (dagger2) 中使用,以确保我的改造客户端使用通用 WrapperResponse 从服务器解包任何答案,最后,我可以将改造方法编写为

    @GET("locations")
    Observable<List<Location>> getLocation();
    

    List 正是我想要获得的结果:直接来自 Retrofit 响应的对象列表,我可以使用 RxJava 进一步详细说明。

    谢谢大家。

    【讨论】:

      【解决方案3】:

      据我所知,没有办法做到这一点。为了获得最佳实践,您应该创建一个Facade 层(可能是一个ApiManager 类)来管理您的所有API。在这种情况下,您可以使用map/flatMap 将您的ObjectResponse 映射到SingleObject,例如:

      public Observable<List<SingleObject>> getSingleObjects(){
          return ServiceGenerator.getApiMethods().getObjectResponse.map(new Function<ObjectResponse, List<SingleObject>>() {
              @Override
              public List<SingleObject> apply(ObjectResponse response) throws Exception {
                  return reponse.getListSingleObjects();
              }
          })
      }
      

      【讨论】:

      • 谢谢。所以,如果我理解正确,你建议我向我的 apiManager 类(我已经有)添加一个方法,在你的例子中是 map,这会让我转换ObjectResponse 到 Observable,所以我可以坚持使用 Retrofit 回调并在其 onResponse 方法中使用 Observable>。基本上我已经在 Retrofit 回调中完成了。问题是来自服务器的 Json 可能与我发布的 Json 不同,所以我必须为每种类型的响应编写 Function 方法。我对么?如果我误解了,请纠正我。再次感谢。
      • 有办法做到这一点。您可以查看this 的答案,尽管您提到的方法也是一种防弹解决方案。
      猜你喜欢
      • 2018-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-25
      • 2013-01-06
      • 2018-09-25
      • 1970-01-01
      相关资源
      最近更新 更多