본문 바로가기

안드로이드

[안드로이드] RxJava3CallAdapterFactory

 

 

이번 포스팅에서는 RxJava3CallAdapterFactory에 대한 내용을 알아보고자 한다.

 

 

 

안드로이드에서 Http 통신을 할 때 대부분 Retrofit 라이브러리를 사용한다.

 

만약 Retrofit과 Rxjava3를 함께 사용한다면 한 번쯤은 이런 에러를 경험했을 수 있다.

 

아래와 같이

Retrofit 인스턴스와 UserApi 인터페이스를 정의했다고 가정해 보자.

 

val retrofit = Retrofit.Builder()
    .baseUrl("https://openapi/base")
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()

interface UserApi {
    @GET("api/v1/users")
    fun getUser(): Single<User>
}

 

이후 getUser 함수를 호출하면 아래와 같은 에러가 발생하는 것을 볼 수 있다.

 

java.lang.IllegalArgumentException: Unable to create call adapter for io.reactivex.rxjava3.core.Single

 

Retrofit에 RxJava3CallAdapterFactory를 추가하지 않고 

Service API 인터페이스의 메서드 타입을 Single로 지정했기 때문에 발생한 오류이다.

 

retrofit의 인스턴스를 생성할 때 아래와 같이 CallAdapterFactory에 RxJava3CallAdapterFactory를 추가하면 더 이상 에러가 발생하지 않는다.

 

val retrofit = Retrofit.Builder()
    .baseUrl("https://openapi/base")
    .addConverterFactory(GsonConverterFactory.create(gson))
    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
    .build()

 

 

RxJava3CallAdapterFactory가 무엇이기에 이런 상황이 발생하는 것일까?

 

 

기본적으로 Retrofit은 Builder를 통해 인스턴스를 생성할 때 callAdapterFactory를 추가해주지 않으면

Retrofit 코드 내부적으로 Platform 클래스의 defaultCallAdapterFactories 함수를 호출해서 

CallAdapter.Factory 타입의 DefaultCallAdapterFactory를 디폴트로 넣어준다.

 

// Retrofit.java
public Retrofit build() {
    // Make a defensive copy of the adapters and add the default Call adapter.
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
}


// Platform.java
List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
    @Nullable Executor callbackExecutor) {
  DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
  return hasJava8Types
      ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
      : singletonList(executorFactory);
}

 

 

디폴트로 추가된 DefaultCallAdapterFactory는 HTTP 통신의 response 데이터 객체를

RxJava3가 제공하는 Observable, Flowable, Single, Completable, Maybe 타입으로 받아올 수 없다.

 

RxJava3CallAdapterFactory를 추가하지 않은 채로 API 인터페이스 메서드를 호출하면

내부적으로 아래와 같은 순서로 함수들을 호출한다.

 

Retrofit 내부 loadServiceMethod 함수 -> ServiceMethod의 parseAnnotations 함수 ->  HttpServiceMethod의 parseAnnotations 함수 -> createCallAdapter 함수 -> Retrofit 내부의 callAdapter 함수 -> nextCallAdapter 함수

 

 

코드를 통해 조금 더 알아보면,

 

HttpServiceMethod 클래스 내의

createCallAdapter 함수에서는 파라미터로 받아온 retrofit 인스턴스를 통해 callAdapter 함수를 호출한다.

 

try ~ catch 문으로 작성되어 있고 CallAdapter를 리턴하는 과정에서 RuntimeException이 발생하면 methodError를 던진다.

 

// HttpServiceMethod.java
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
    Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
  try {
    //noinspection unchecked
    return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
  } catch (RuntimeException e) { // Wide exception range because factories are user code.
    throw methodError(method, e, "Unable to create call adapter for %s", returnType);
  }
}

 

 

callAdapter 함수에 API 인터페이스 메서드의 return 타입과 어노테이션을 인자로 넣어준다.

 

// Retrofit.java

// 사용가능한 callAdapterFactories에서 returnType에 대한 callAdapter를 반환한다
// returnType에 대해 사용가능한 call adapter가 없는 경우 IllegalArgumentException를 던진다
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
  return nextCallAdapter(null, returnType, annotations);
}

// returnType에 대해 사용가능한 call adapter가 없는 경우 IllegalArgumentException를 던진다
public CallAdapter<?, ?> nextCallAdapter(
    @Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
  Objects.requireNonNull(returnType, "returnType == null");
  Objects.requireNonNull(annotations, "annotations == null");

  int start = callAdapterFactories.indexOf(skipPast) + 1;
  for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
    CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
    if (adapter != null) {
      return adapter;
    }
  }

  StringBuilder builder =
      new StringBuilder("Could not locate call adapter for ").append(returnType).append(".\n");
  if (skipPast != null) {
    builder.append("  Skipped:");
    for (int i = 0; i < start; i++) {
      builder.append("\n   * ").append(callAdapterFactories.get(i).getClass().getName());
    }
    builder.append('\n');
  }
  builder.append("  Tried:");
  for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
    builder.append("\n   * ").append(callAdapterFactories.get(i).getClass().getName());
  }
  throw new IllegalArgumentException(builder.toString());
}

 

 

아래 코드는 nextCallAdapter 함수 안에서

returnType에 대한 CallAdapter를 찾으면 해당하는 adapter를 리턴하는 코드이다.

 

int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
  CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
  if (adapter != null) {
    return adapter;
  }
}

 

 

해당하는 CallAdapter를 찾지 못하면 다음 코드로 넘어가게 된다.

 

StringBuilder builder =
      new StringBuilder("Could not locate call adapter for ").append(returnType).append(".\n");
if (skipPast != null) {
  builder.append("  Skipped:");
  for (int i = 0; i < start; i++) {
    builder.append("\n   * ").append(callAdapterFactories.get(i).getClass().getName());
  }
  builder.append('\n');
}
builder.append("  Tried:");
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
  builder.append("\n   * ").append(callAdapterFactories.get(i).getClass().getName());
}
throw new IllegalArgumentException(builder.toString());

 

여기서 IllegalArgumentException을 던지게 되어 에러가 발생한다.

 

 

이제 Retrofit에 아래와 같이 RxJava3CallAdapterFactory를 추가해 보자.

 

RxJava3CallAdapterFactory는 Retrofit이 Call 객체가 아닌

RxJava3가 제공하는 Observable, Flowable, Single, Completable, Maybe 타입으로 받아올 수 있도록 한다.

 

val retrofit = Retrofit.Builder()
    .baseUrl("https://openapi/base")
    .addConverterFactory(GsonConverterFactory.create(gson))
    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
    .build()

 

 

RxJava3CallAdapterFactory 객체는 create 함수를 통해 생성할 수 있다.

 

생성자가 private이기 때문에 Factory 패턴으로 객체를 생성한다.

 

3개의 팩토리 패턴 함수를 통해 객체를 생성할 수 있다.

 

public final class RxJava3CallAdapterFactory extends CallAdapter.Factory {

  // 디폴트로 백그라운드 스레드에서 실행되는 비동기적인 observable을 생성하는 인스턴스를 리턴
  // subscribeOn 함수가 적용되지 않는다.
  public static RxJava3CallAdapterFactory create() {
    return new RxJava3CallAdapterFactory(null, true);
  }

  // 디폴트로 scheduler에서 동작하지 않는 동기적인 observable을 생성하는 인스턴스를 리턴
  // subscribeOn 함수가 scheduler를 변경할 수 있다
  public static RxJava3CallAdapterFactory createSynchronous() {
    return new RxJava3CallAdapterFactory(null, false);
  }

  // 파라미터로 받은 scheduler로 동작하는 동기적인 observable을 생성해 리턴
  @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
  public static RxJava3CallAdapterFactory createWithScheduler(Scheduler scheduler) {
    if (scheduler == null) throw new NullPointerException("scheduler == null");
    return new RxJava3CallAdapterFactory(scheduler, false);
  }

  private final @Nullable Scheduler scheduler;
  private final boolean isAsync;

  private RxJava3CallAdapterFactory(@Nullable Scheduler scheduler, boolean isAsync) {
    this.scheduler = scheduler;
    this.isAsync = isAsync;
  }
}

 

각각의 함수 이름에서 알 수 있듯이

create 함수는 디폴트로 백그라운드 스레드에서 실행되는 비동기적인 observable을 생성하는 Factory를 리턴한다.

createSynchronous 함수는 디폴트로 scheduler에서 동작하지 않는 동기적인 observable을 생성하는 Factory를 리턴한다.

createWithScheduler 함수는 파라미터로 넘겨받은 scheduler로 동작하는 동기적인 observable을 생성하는 Factory를 리턴한다.

 

 

 

 

'안드로이드' 카테고리의 다른 글

[안드로이드] Retrofit 분석  (0) 2023.03.11
[Android] Fragment ViewBinding 사용 시 주의할 점  (0) 2023.02.12
[Android] Fragment Lifecycle  (0) 2023.02.12
[안드로이드] ListAdapter  (0) 2023.02.10