如何使用 Retrofit 和 Kotlin 协程下载 PDF 文件?

Coo*_*ind 7 android kotlin retrofit2 kotlin-coroutines

我看到了诸如如何使用 Retrofit 库在 Android 中下载文件之类的主题,他们使用@Streaming和 RxJava / 回调。

我有 Kotlin、协程、改造 2.6.0 和/sf/answers/3953175411/ 中的查询:

@FormUrlEncoded
@Streaming
@POST("export-pdf/")
suspend fun exportPdf(
    @Field("token") token: String
): ExportResponse
Run Code Online (Sandbox Code Playgroud)

我有一个改造客户:

retrofit = Retrofit.Builder()
    .baseUrl(SERVER_URL)
    .client(okHttpClient)
    .build()

service = retrofit.create(Api::class.java)
Run Code Online (Sandbox Code Playgroud)

如果令牌参数正确,则查询返回 PDF 文件:

%PDF-1.4
%????
...
Run Code Online (Sandbox Code Playgroud)

如果出错,它将返回带有错误描述的 JSON:

{
    "success": 0,
    "errors": {
        "message": "..."
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,ExportResponse 是一个包含 JSON 字段的数据类,POJO。

我无法访问文件数据

Response response = restAdapter.apiRequest();

try {
    //you can now get your file in the InputStream
    InputStream is = response.getBody().in();
} catch (IOException e) {
    e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

因为ExportResponse是一个数据类,所以val response: ExportResponse = interactor.exportPdf(token)会返回数据,而不是Retrofit对象。

And*_*ana 10

您可以更改exportPdfto的返回类型,Call<ResponseBody>然后检查响应代码。如果没问题,则将正文作为流读取。如果不是,则尝试反序列化 ExportResponse。我猜它看起来像这样:

val response = restAdapter.apiRequest().execute()
if (response.isSuccessful) {
    response.body()?.byteStream()//do something with stream
} else {
    response.errorBody()?.string()//try to deserialize json from string
}
Run Code Online (Sandbox Code Playgroud)

更新

这是我的测试的完整列表:

import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.http.GET
import retrofit2.http.Url
import java.io.File
import java.io.InputStream

fun main() {
    val queries = buildQueries()
    check(queries, "http://127.0.0.1:5000/error")
    check(queries, "http://127.0.0.1:5000/pdf")
}

private fun check(queries: Queries, url: String) {
    val response = queries.exportPdf(HttpUrl.get(url)).execute()
    if (response.isSuccessful) {
        response.body()?.byteStream()?.saveToFile("${System.currentTimeMillis()}.pdf")
    } else {
        println(response.errorBody()?.string())
    }
}

private fun InputStream.saveToFile(file: String) = use { input ->
    File(file).outputStream().use { output ->
        input.copyTo(output)
    }
}

private fun buildRetrofit() = Retrofit.Builder()
    .baseUrl("http://127.0.0.1:5000/")
    .client(OkHttpClient())
    .build()

private fun buildQueries() = buildRetrofit().create(Queries::class.java)

interface Queries {
    @GET
    fun exportPdf(@Url url: HttpUrl): Call<ResponseBody>
}
Run Code Online (Sandbox Code Playgroud)

这是用 Flask 构建的简单服务器:

val response = restAdapter.apiRequest().execute()
if (response.isSuccessful) {
    response.body()?.byteStream()//do something with stream
} else {
    response.errorBody()?.string()//try to deserialize json from string
}
Run Code Online (Sandbox Code Playgroud)

对我来说一切正常

更新 2

看起来你必须在你的 Api 中写这个:

@FormUrlEncoded
@Streaming // You can also comment this line.
@POST("export-pdf/")
fun exportPdf(
    @Field("token") token: String
): Call<ResponseBody>
Run Code Online (Sandbox Code Playgroud)