Благодаря @AndreiTanana я обнаружил ошибку.Проблема была в suspend
в определении запроса.Все остальные запросы сохраняют свой модификатор suspend
, но этот запрос удалил его.Я изменил код так.
interface Api {
@FormUrlEncoded
@Streaming
@POST("export-pdf/")
fun exportPdf(
@Field("token") token: String
): Call<ResponseBody>
// Any another request. Note 'suspend' here.
@FormUrlEncoded
@POST("reject/")
suspend fun reject(): RejectResponse
}
Затем в его реализации ApiImpl:
class ApiImpl : Api {
private val retrofit by lazy { ApiClient.getRetrofit().create(Api::class.java) }
override fun exportPdf(
token: String
): Call<ResponseBody> =
retrofit.exportPdf(token)
override suspend fun reject(): RejectResponse =
// Here can be another instance of Retrofit.
retrofit.reject()
}
Модифицированный клиент:
class ApiClient {
companion object {
private val retrofit: Retrofit
init {
val okHttpClient = OkHttpClient().newBuilder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build()
val gson = GsonBuilder().setLenient().create()
retrofit = Retrofit.Builder()
.baseUrl(SERVER_URL)
.client(okHttpClient)
// .addConverterFactory(GsonConverterFactory.create(gson)) - you can add this line, I think.
.build()
}
fun getRetrofit(): Retrofit = retrofit
}
Интерактор:
interface Interactor {
// Note 'suspend' here. This is for coroutine chain.
suspend fun exportPdf(
token: String
): Call<ResponseBody>
}
class InteractorImpl(private val api: Api) : Interactor {
override suspend fun exportPdf(
token: String
): Call<ResponseBody> =
api.exportPdf(token)
}
Затем во фрагменте:
private fun exportPdf(view: View, token: String) {
showProgress(view)
launch(Dispatchers.IO) {
try {
val response = interactor.exportPdf(token).execute()
var error: String? = null
if (response.headers().get("Content-Type")?.contains(
"application/json") == true) {
// Received JSON with an error.
val json: String? = response.body()?.string()
error = json?.let {
val export = ApiClient.getGson().fromJson(json,
ExportPdfResponse::class.java)
export.errors?.common?.firstOrNull()
} ?: getString(R.string.request_error)
} else {
// Received PDF.
val buffer = response.body()?.byteStream()
if (buffer != null) {
val file = context?.let { createFile(it, "pdf") }
if (file != null) {
copyStreamToFile(buffer, file)
launch(Dispatchers.Main) {
if (isAdded) {
hideProgress(view)
}
}
}
}
}
if (error != null) {
launch(Dispatchers.Main) {
if (isAdded) {
hideProgress(view)
showErrorDialog(error)
}
}
}
} catch (e: Exception) {
launch(Dispatchers.Main) {
if (isAdded) {
showErrorDialog(getString(R.string.connection_timeout))
hideProgress(view)
}
}
}
}
}
СТАРЫЙ ОТВЕТ
Этот ответ не применим к двоичным файлам, таким как PDF.Возможно, его можно использовать с текстовыми файлами.
ApiClient:
val okHttpClient = OkHttpClient().newBuilder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build()
retrofit = Retrofit.Builder()
.baseUrl(SERVER_URL)
.client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create()) // It is used to convert Response<String>.
// .addConverterFactory(GsonConverterFactory.create(gson)) - you can also add this line.
.build()
Api:
@FormUrlEncoded
@Streaming // You can also comment this line.
@POST("export-pdf/")
suspend fun exportPdf(
@Field("token") token: String
): Response<String>
Интегратор:
private val service by lazy {
ApiClient.getRetrofit().create(Api::class.java)
}
suspend fun exportPdf(
token: String
): Response<String> =
service.exportPdf(token)
Фрагмент:
private fun exportPdf(token: String) {
launch {
try {
val response = interactor.exportPdf(token)
var error: String? = null
if (response.headers().get("Content-Type")?.contains(
"application/json") == true) {
// Received JSON with an error.
val json: String? = response.body()
error = json?.let {
val export = gson.fromJson(json, ExportPdfResponse::class.java)
export.errors?.message?.firstOrNull()
} ?: getString(R.string.request_error)
} else {
// Received PDF.
val buffer: ByteArrayInputStream? = response.body()?.byteInputStream()
if (buffer != null) {
val file = context?.let { createFile(it, "pdf") }
if (file != null) {
copyStreamToFile(buffer, file)
}
}
}
} catch (e: Exception) {
launch(Dispatchers.Main) {
if (isAdded) {
showErrorDialog(getString(R.string.connection_timeout))
}
}
}
}
}
Я проверяю заголовок ответа, затем определяю, является ли он JSON или PDF.В случае PDF я использую ScalarsConverterFactory
для преобразования ответа в поток байтов.copyStreamToFile
копирует байты в файл, я нашел его в https://stackoverflow.com/a/56074084/2914140.
Вместо 69 857 байтов он создал файл 113 973 байта, который не отображается.Я видел внутри, он изменил все нелатинские символы с другими кодами.
Я пытался написать Response<Any>
в методах, но это привело к ошибке: "java.lang.IllegalArgumentException: Невозможно создать конвертер длякласс java.lang.Object для метода Api.exportPdf ".Я пытался написать свой ConverterFactory for Any, но безуспешно.
Также изменил этот код и получил ошибки:
- java.lang.IllegalStateException: Невозможно прочитать необработанное тело ответапреобразованное тело.
- java.lang.IllegalArgumentException: невозможно создать конвертер для класса java.lang.String для метода Api.exportPdf
- Невозможно создать конвертер для retrofit2.Call для метода Api.exportPdf.