Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RomanPozdeev/4cfb5a3693f5969b98e5927a412bf18e to your computer and use it in GitHub Desktop.
Save RomanPozdeev/4cfb5a3693f5969b98e5927a412bf18e to your computer and use it in GitHub Desktop.
retrofit coroutines workaround
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import retrofit2.Retrofit
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
private typealias Invoke = suspend (proxy: Any, method: Method, args: Array<*>) -> Any?
object RetrofitFix {
@Suppress("UNCHECKED_CAST")
fun <T> create(retrofit: Retrofit, context: CoroutineContext, service: Class<T>): T {
val delegate = retrofit.create(service)
val invoke: Invoke = { _: Any, method: Method, args: Array<*> ->
applyDispatcher(context) {
method.invoke(delegate, *args)
}
}
val invocationHandler = suspendInvocationHandler(invoke)
return Proxy.newProxyInstance(
service.classLoader,
arrayOf<Class<*>>(service),
invocationHandler
) as T
}
private suspend inline fun applyDispatcher(
context: CoroutineContext,
crossinline method: () -> Any?
) {
withContext(context) {
method()
}
}
private fun suspendInvocationHandler(block: Invoke): InvocationHandler {
return InvocationHandler { proxy, method, args ->
val cont = args?.lastOrNull() as? Continuation<*>
if (cont == null) {
val methodArgs = args.orEmpty()
runBlocking {
block(proxy, method, methodArgs)
}
} else {
@Suppress("UNCHECKED_CAST")
val suspendInvoker = block as (Any, Method, Array<*>?, Continuation<*>) -> Any?
suspendInvoker(proxy, method, args, cont)
}
}
}
}
@RomanPozdeev
Copy link
Author

RomanPozdeev commented Aug 3, 2022

старая добрая java спешит на помощь

public class RetrofitFix {
    @SuppressWarnings("unchecked")
    public static <T> T create(final Retrofit retrofit, final CoroutineContext context, final Class<T> service) {
        final T delegate = retrofit.create(service);
        return (T)
            Proxy.newProxyInstance(
                service.getClassLoader(),
                new Class<?>[]{service},
                new InvocationHandler() {
                    @Nullable
                    @Override
                    public Object invoke(final Object proxy, final Method method, @Nullable final Object[] args)
                        throws Throwable {
                        // If the method is a method from Object then defer to normal invocation.
                        if (method.getDeclaringClass() == Object.class) {
                            return method.invoke(this, args);
                        }
                        if (args == null || !(args[args.length - 1] instanceof Continuation)) {
                            return method.invoke(delegate, args);
                        }
                        Continuation<?> methodContinuation = (Continuation<?>) args[args.length - 1];
                        try {
                            return BuildersKt.withContext(context,
                                (coroutineScope, continuation) -> {
                                    try {
                                        final Object[] newArgs = Arrays.copyOf(args, args.length);
                                        newArgs[newArgs.length - 1] = continuation;
                                        return method.invoke(delegate, newArgs);
                                    } catch (IllegalAccessException | InvocationTargetException e) {
                                        Timber.e(e);
                                        return null;
                                    }
                                },
                                methodContinuation);
                        } catch (Throwable throwable) {
                            methodContinuation.resumeWith(new Result.Failure(throwable));
                            return IntrinsicsKt.getCOROUTINE_SUSPENDED();
                        }
                    }
                });
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment