Я добавил адаптер вызовов в приложение, чтобы моя сетевая служба оборачивала все, что она возвращает, в Result и сопоставляла все исключения с моим CustomException.
Удивительно, что в режиме отладки все работает нормально.
Проблема:
Даже когда я подключаю средство ведения журнала OkHttp в производственной сборке, я вижу, что запрос возвращает правильный ответ (не null), но в репозитории получаю null:
authService.login(data).fold(onSuccess = { response -> // returns null }
Можно ли заставить это работать с помощью системной оболочки Result?
Спасибо за помощь
Мои попытки решить проблему:
Предполагаю, что это может быть связано с очисткой файлов с помощью R8/ProGuard. Я тщательно проверил, добавлена ли аннотация @keep в нужных местах. Я также попробовал использовать собственную оболочку ApiResult вместо Result, которую, предположительно, R8/ProGuard может неправильно очистить, но у меня возникли серьезные трудности с ее реализацией.
Код
Это мой собственный адаптер вызовов:
@Keep
@Suppress("UNCHECKED_CAST")
class ResultCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array,
retrofit: Retrofit
): CallAdapter? {
if (getRawType(returnType) != Call::class.java || returnType !is ParameterizedType) {
return null
}
// get inner type
val upperBound = getParameterUpperBound(0, returnType)
return if (upperBound is ParameterizedType && upperBound.rawType == Result::class.java) {
// return new call adapter that change Call to ResultCall
object : CallAdapter {
override fun responseType(): Type = getParameterUpperBound(0, upperBound)
override fun adapt(call: Call): Call =
ResultCall(call) as Call
}
} else {
null
}
}
}
Реализация вызова:
@Keep
class ResultCall(val delegate: Call) :
Call {
override fun enqueue(callback: Callback) {
delegate.enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
// server response status code 200–299
if (response.isSuccessful) {
Timber.d("response: ${response.body()}")
callback.onResponse(
this@ResultCall,
Response.success(
response.code(),
Result.success(response.body()!!)
)
)
} else {
Timber.d("response has error")
// server response status code 404, 500
val exception = when (response.code()) {
401, 403 -> CustomException.Unauthorized()
404 -> CustomException.NotFound()
else -> {
if (response.message().isEmpty()) CustomException.Unknown()
else CustomException.Custom(response.message())
}
}
callback.onResponse(
this@ResultCall,
Response.success(
Result.failure(
exception
)
)
)
}
}
// call with connection errors
override fun onFailure(call: Call, t: Throwable) {
val exception = when (t) {
is IOException -> CustomException.NoConnection()
is HttpException -> CustomException.Unknown()
else -> {
if (t.localizedMessage.isNullOrEmpty()) CustomException.Unknown()
else CustomException.Custom(t.localizedMessage!!)
}
}
callback.onResponse(
this@ResultCall,
Response.success(Result.failure(exception))
)
}
}
)
}
override fun execute(): Response =
Response.success(Result.success(delegate.execute().body()!!))
override fun isExecuted(): Boolean = delegate.isExecuted
override fun cancel() = delegate.cancel()
override fun isCanceled(): Boolean = delegate.isCanceled
override fun clone(): Call = ResultCall(delegate.clone())
override fun request(): Request = delegate.request()
override fun timeout(): Timeout = delegate.timeout()
}
Сетевая служба: (я добавил аннотацию сохранения к LoginRequest и LoginResponse.)
@Keep
interface AuthService {
@POST("/Myapp/Login")
suspend fun login(@Body body: LoginRequest) : Result // Here is the problem, it returns null instead of LoginResponse.
@POST("/Myapp/Logout")
suspend fun resetToken() : Result
@GET
suspend fun resetSynodiPassword(
@Url absoluteUrl: String,
@Query("login") email: String
): Result
}
Файл ProGuard:
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes InnerClasses
-keepattributes EnclosingMethod
-keep class kotlin.Metadata { *; }
# remove logging
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
# Keep all fields for Gson reflection
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName *;
@com.google.gson.annotations.Expose *;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
Подробнее здесь: https://stackoverflow.com/questions/798 ... all-adapte
Мобильная версия