将改造回调值转换为 return 封装对象

Convert retrofit callback value to return enveloped object

我的问题是关于 Android 使用 RxJava 来操作来自 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)
                }
            });

据我所知,没有办法。为了获得最佳实践,您应该创建一个 Facade 层(可能是 ApiManager class)来管理您的所有 API。在这种情况下,您可以使用 map/flatMapObjectResponse 映射到 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();
        }
    })
}

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

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

定义了一个信封 class - class 带有一些您不关心的额外字段:

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

在此 POJO 字段中,metaList<Notification> 是从后端 return 编辑的,但在 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 界面而不是 returning Single<OriginalResponse> return Single<List<ActualData>> 直接:

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 }
  }
}

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

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

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

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

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

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();

    }
}

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

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

其中 List 正是我想要获得的结果:直接来自 Retrofit 响应的对象列表,我可以用 RxJava 进一步阐述。

谢谢大家