我在 Kotlin Multiplatform 中开发了一个应用程序,在那里我尝试将常规库实现到网络和本地存储:带有 SqlDelight 的 Ktor。
在设置正确的配置以使 KMM 编译正常后,我尝试使用 Ktor 库连接 API,但虽然我的实现返回 200 代码响应,但我收到上述标题错误:
response failed with exception: kotlinx.serialization.json.JsonDecodingException: Unexpected JSON token at offset 0: Expected '[, kind: LIST'.
关于我的 API.class
internal val kotlinxSerializer = KotlinxSerializer(
Json(
JsonConfiguration(isLenient = false, ignoreUnknownKeys = true, encodeDefaults = false)
)
)
internal val ktorClient = HttpClient {
defaultRequest {
header("Accept-Version","v1")
header("Authorization", "Client-ID $CLIENT_ID")
}
install(JsonFeature) {
serializer = kotlinxSerializer
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
}
private const val CLIENT_ID : String = "7DQCWc0Hr_GGIOUeMAVKxqvz9lsVtCpvauRvXJnNq_E"
internal const val UNSPLASH_URL = "https://api.unsplash.com/search/photos"
//TODO("La resquest al endpoint se inicializa mal")
//TODO("Tras ejecutar la llamada de red en postman, el error es que no le estoy pasando el access token.")
/**
2021-12-18 17:57:25.636 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - REQUEST: https://api.unsplash.com/search/photos?query=query&page=page&per_page=per_page
2021-12-18 17:57:25.636 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
2021-12-18 17:57:25.638 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - COMMON HEADERS
2021-12-18 17:57:25.638 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Accept: Accept-Version: v1; application/json
2021-12-18 17:57:25.638 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
2021-12-18 17:57:25.639 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - CONTENT HEADERS
2021-12-18 17:57:25.640 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY Content-Type: null
2021-12-18 17:57:25.704 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY START
2021-12-18 17:57:25.705 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient -
2021-12-18 17:57:25.705 14877-14947/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY END
2021-12-18 17:57:25.974 1547-3158/? D/CompatibilityInfo: mCompatibilityFlags - 0
*/
class UnsplashApi(private val client: HttpClient = ktorClient) {
suspend fun getPets(): List<UnsplashPhoto>? = client.get(UNSPLASH_URL){
//parameter("client_id", CLIENT_ID)
parameter("query", "cats")
parameter("page", 1)
parameter("per_page", 10)
}
//TODO("Actualizar el endpoint para incorporar la funcionalidad de buscar por nombre")
suspend fun getPetsByName(pet: String): List<UnsplashPhoto>? = client.get<List<UnsplashPhoto>>(UNSPLASH_URL){
parameter("query", pet)
parameter("page", 1)
parameter("per_page", 10)
}
}
关于我的存储库,我使用 Ktor 库从 API 恢复数据,如下所示:` PetsRepository.kt
internal expect fun cache(): PetsDatabase
class PetsRepository(
private val api: UnsplashApi,
private val queries: PetsDBQueries = cache().petsDBQueries
) {
constructor() : this(api = UnsplashApi())
/**
* If [force] is set to true, attempt to load data from remote api.
* If remote api is not available. throw [RefreshDataException]
*
* If [force] is set to false, attempt to load data from local cache.
* If local cache is not available, propagate the exception encountered
*/
fun fetchMembersAsFlow(force: Boolean): Flow<List<Pets>> {
return if (force) getMembersFromRemote() else getMembersFromCache()
}
private fun cacheMembers(pets: List<Pets>) {
queries.deleteAll()
pets.forEach { pet ->
queries.insertPet(
pet.id,
pet.description,
pet.url,
pet.email
)
}
}
/**
* Retorna un Flow con una lista de Pets, y guardando en la BD local
* los pets recuperados de la llamada de red.
*/
private fun getMembersFromRemote(): Flow<List<Pets>> {
println("Getting members from remote")
return flow {
val unsplashPhotos = api.getPets()
Log.d("info", "size pets llamada de red: ${unsplashPhotos!!.size}")
Log.d("info", "pets $unsplashPhotos")
//TODO(""Convert UnplashPhoto o Pets")
var pets : List<Pets> = unsplashPhotos!!.map {
Pets(it.id!!.toLong(), it.description!!, it.urls.full, it.user.name)
}
cacheMembers(pets)
emit(pets)
}
.catch { error(RefreshDataException()) }
.flowOn(applicationDispatcher)
}
private fun getMembersFromCache(): Flow<List<Pets>> {
println("Getting members from cache")
fun loadMembers() = queries.selectAll()
.executeAsList()
.map { Pets(id = it.id, description = it.description, url = it.url, email = it.emailUser) }
return flow { emit(loadMembers()) }
.catch { error(RefreshDataException()) }
.flowOn(applicationDispatcher)
}
}
关于我的模型。
宠物.kt
@Serializable
class Pets (
@SerialName("id") val id: Long,
@SerialName("description")val description: String,
@SerialName("url")val url: String,
@SerialName("email")val email: String
)
UnplashPhoto.kt
@Serializable
data class UnsplashPhoto(
@SerialName ("id")val id: String?,
@SerialName ("description")val description: String?,
@SerialName ("urls")val urls: UnsplashPhotoUrls,
@SerialName ("user")val user: UnsplashUser
)
@Serializable
data class UnsplashPhotoUrls(
@SerialName("raw") val raw: String,
@SerialName("full") val full: String,
@SerialName("regular") val regular: String,
@SerialName("small") val small: String,
@SerialName("thumb") val thumb: String
)
@Serializable
data class UnsplashUser(
val name: String,
val username: String,
val attributionUrl : String
)
//get() =
// "https://unsplash.com/$username?utm_source=ImageSearchApp&utm_medium=referral"
另一种结构是常规的 MVVM 模式,我使用 ViewModel 以 Flow 的形式传递数据。但主要的一点是,Ktor 似乎需要在某些时候反序列化响应,我想我应该处于 UnplashApi.kt 级别。
我也在阅读一些论坛,以及他们建议的不同解决方案,例如将“isLenient = false”和“encodeDefault = false”放在 KotlinxSerializer 的 JsonConfiguration 中。
我希望你能提供帮助,如果这样,请提前感谢!
[编辑]
在@AlekseiTirman 的建议下,我像这样更改了我的 UnplashhApi.kt 的代码,但是虽然响应代码是 200,并且数据正确加载到响应中,但仍然不起作用:
UnplashAPI.kt
class UnsplashApi(private val client: HttpClient = ktorClient) {
suspend fun getPets(): UnsplashReponse? = client.get(UNSPLASH_URL){
//parameter("client_id", CLIENT_ID)
parameter("query", "cats")
parameter("page", 1)
parameter("per_page", 10)
}
添加了 2 张显示数据的 logcat 图片:
[编辑]
调试应用程序后,我看到数据流是正确的,直到我尝试将 UnsplashPhoto 列表转换为存储库类中的宠物列表,当我尝试映射它时。