0

我在 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 列表转换为存储库类中的宠物列表,当我尝试映射它时。

4

0 回答 0