1

我正在开发一个 Kotlin 多平台应用程序,我使用 Ktor 来进行网络调用,并使用 SqlDelite 来管理本地数据源。

因此,当您创建 KMM 项目时,我尝试实现与构建 Android Studio 相同的结构。

我的问题是我用来异步连接的 api,我需要传递两个标头才能在其中进行身份验证。我尝试在我的 HttpClient 中使用标头,并在我的客户端的 get 方法中使用参数。

我不知道这是正确的方法,还是看起来不是。

关于我的后端设置有以下类:

UnsplashApi.kt

class UnsplashApi(private val client: HttpClient = ktorClient) {
  suspend fun getPets(): List<UnsplashPhoto> = client.get(UNSPLASH_URL){
    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(UNSPLASH_URL){
    headers{
      append(HttpHeaders.Accept, "v1")
      append("Authorization:","Client-ID $CLIENT_ID")
    }
    parameter("query", pet)
    parameter("page", 1)
    parameter("per_page", 10)
  }
}
internal val kotlinxSerializer = KotlinxSerializer(
  Json(
    JsonConfiguration(isLenient = true, ignoreUnknownKeys = true)
  )
)
internal val ktorClient = HttpClient {
  defaultRequest {
    header("Authorization:", "Client-ID $CLIENT_ID")
    header("Accept-Version:","v1")
  }
  install(JsonFeature) {
    serializer = kotlinxSerializer
  }
  install(Logging) {
    logger = Logger.DEFAULT
    level = LogLevel.ALL
  }
}

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() as Flow<List<Pets>> 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<UnsplashPhoto>> {
    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(api.getPets())
    }
        .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)
  }
}

关于我的 androidApp 文件夹分类:

PetsViewModel.kt

class PetsViewModel(
  private val repository: PetsRepository
) : ViewModel() {

  private val _pets = MutableLiveData<List<Pets>>()
  val pets: LiveData<List<Pets>> = _pets

  private val _error = MutableLiveData<Throwable>()
  val error: LiveData<Throwable> = _error

  private val _isRefreshing = MutableLiveData<Boolean>()
  val isRefreshing: LiveData<Boolean> = _isRefreshing

  init {
    loadPets()
  }

  fun loadPets(force: Boolean = false) {
    viewModelScope.launch {
      repository.fetchMembersAsFlow(force)
        .onStart {
          _isRefreshing.value = true
        }.onCompletion {
          _isRefreshing.value = false
        }.catch {
          _error.value = it
          Log.d("info", "STACKTRACE: ${it.stackTrace.contentToString()} + ${it.cause}")
        }.collect {
          _pets.value = it
        }
    }
  }
}

宠物适配器.kt


class PatsAdapter(var pets: List<Pets>) : RecyclerView.Adapter<PatsAdapter.MemberViewHolder>() {
  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MemberViewHolder {
    val itemView =
      LayoutInflater.from(parent.context).inflate(R.layout.list_item_member, parent, false)
    return MemberViewHolder(itemView)
  }

  override fun getItemCount() = pets.size

  override fun onBindViewHolder(holder: MemberViewHolder, position: Int) {
    holder.bind(pets[position])
  }

  class MemberViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(pet: Pets) {
      Picasso.get().load(pet.url).into(itemView.memberAvatar)
      itemView.tvItemDescription.text = pet.description
      itemView.tvItemAutor.text = pet.email
    }
  }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

  private val repository by lazy {
    (application as Application).membersRepository
  }

  private val viewModel by lazy { PetsViewModel(repository) }

  private lateinit var adapter: PatsAdapter

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(layout.activity_main)

    platformMessage.text = createPlatformMessage()

    setupRecyclerView()

    viewModel.pets.observe(this, Observer {
      if (it.isEmpty()) {
        Toast.makeText(this, R.string.empty_cache, Toast.LENGTH_LONG).show()
      } else {
        showData(it)
      }
    })

    viewModel.error.observe(this, Observer {
      showError(it)
    })

    viewModel.isRefreshing.observe(this, Observer {
      pullToRefresh.isRefreshing = it
    })

    pullToRefresh.setOnRefreshListener {
      viewModel.loadPets(force = true)
    }
  }

  private fun showData(pets: List<Pets>) {
    adapter.pets = pets
    adapter.notifyDataSetChanged()
  }

  private fun showError(error: Throwable) {
    val errorMessage = when (error) {
      is RefreshDataException -> getString(string.refresh_data_error)
      else -> getString(string.unknown_error)
    }
    Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
  }

  private fun setupRecyclerView() {
    membersRecyclerView.layoutManager = LinearLayoutManager(this)
    adapter = PatsAdapter(emptyList())
    membersRecyclerView.adapter = adapter
  }
}

应用程序.kt

class Application : Application() {
  val membersRepository by lazy { PetsRepository() }

  override fun onCreate() {
    super.onCreate()

    appContext = this

    if (BuildConfig.DEBUG) {
      Timber.plant(Timber.DebugTree())
    }
  }
}

我不包括 sqlDelite 的部分,因为与这个问题的主要目标无关,这是 Api.kt 类的结构,其中 Ktor 用于建立网络连接。

[编辑]

添加了请求的logcat:

2021-12-21 11:33:23.706 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - REQUEST: https://api.unsplash.com/?client_id=7DQCWc0Hr_GGIOUeMAVKxqvz9lsVtCpvauRvXJnNq_E%2Fsearch%2Fphotos&query=cats&page=1&per_page=10
2021-12-21 11:33:23.707 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
2021-12-21 11:33:23.708 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - COMMON HEADERS
2021-12-21 11:33:23.709 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Accept-Version: v1
2021-12-21 11:33:23.709 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Accept: application/json
2021-12-21 11:33:23.709 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
2021-12-21 11:33:23.709 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - CONTENT HEADERS
2021-12-21 11:33:23.711 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY Content-Type: null
2021-12-21 11:33:23.763 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY START
2021-12-21 11:33:23.763 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - 
2021-12-21 11:33:23.763 6813-6866/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY END
2021-12-21 11:33:23.799 6813-6878/com.jshvarts.kmp.android D/NetworkSecurityConfig: No Network Security Config specified, using platform default
2021-12-21 11:33:23.800 6813-6878/com.jshvarts.kmp.android I/DpmTcmClient: RegisterTcmMonitor from: $Proxy0

[编辑]

像这样更改我的 api.kt 类后:


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(UNSPLASH_URL){
    parameter("query", pet)
    parameter("page", 1)
    parameter("per_page", 10)
  }
}
internal const val UNSPLASH_URL = "https://api.unsplash.com/search/photos"
private const val CLIENT_ID : String = ....


internal val kotlinxSerializer = KotlinxSerializer(
  Json(
    JsonConfiguration(isLenient = true, ignoreUnknownKeys = true)
  )
)
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
  }
}

我开始在 logcat 中得到这个:

2021-12-21 11:53:32.715 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - RESPONSE: 200 OK
2021-12-21 11:53:32.716 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
2021-12-21 11:53:32.718 7396-7450/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY Content-Type: application/json
2021-12-21 11:53:32.718 7396-7450/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - BODY START
2021-12-21 11:53:32.725 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - FROM: https://api.unsplash.com/search/photos?query=cats&page=1&per_page=10
2021-12-21 11:53:32.725 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - COMMON HEADERS
2021-12-21 11:53:32.725 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Accept-Ranges: bytes
2021-12-21 11:53:32.725 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Access-Control-Allow-Headers: *
2021-12-21 11:53:32.726 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Access-Control-Allow-Origin: *
2021-12-21 11:53:32.726 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Access-Control-Expose-Headers: Link,X-Total,X-Per-Page,X-RateLimit-Limit,X-RateLimit-Remaining
2021-12-21 11:53:32.726 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Access-Control-Request-Method: *
2021-12-21 11:53:32.726 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Age: 445
2021-12-21 11:53:32.726 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Cache-Control: no-cache, no-store, must-revalidate
2021-12-21 11:53:32.726 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Connection: keep-alive
2021-12-21 11:53:32.726 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Content-Type: application/json
2021-12-21 11:53:32.727 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Date: Tue, 21 Dec 2021 10:53:33 GMT
2021-12-21 11:53:32.727 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Etag: W/"9545b4e1f85112ca6cc4c543c08a7a37"
2021-12-21 11:53:32.727 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Link: <https://api.unsplash.com/search/photos?page=1000&per_page=10&query=cats>; rel="last", <https://api.unsplash.com/search/photos?page=2&per_page=10&query=cats>; rel="next"
2021-12-21 11:53:32.727 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Server: Cowboy
2021-12-21 11:53:32.727 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Strict-Transport-Security: max-age=31536000; includeSubDomains
2021-12-21 11:53:32.727 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Vary: Accept-Encoding, Origin,Authorization,Accept-Language,client-geo-region,Accept
2021-12-21 11:53:32.728 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Via: 1.1 vegur, 1.1 varnish, 1.1 varnish
2021-12-21 11:53:32.728 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> Warning: The tags property in this endpoint is deprecated. https://changelog.unsplash.com/deprecations/2021/07/12/tags-search-deprecation.html
2021-12-21 11:53:32.728 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Android-Received-Millis: 1640084012689
2021-12-21 11:53:32.728 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Android-Response-Source: NETWORK 200
2021-12-21 11:53:32.728 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Android-Selected-Protocol: http/1.1
2021-12-21 11:53:32.728 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Android-Sent-Millis: 1640084012597
2021-12-21 11:53:32.729 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Cache: MISS, HIT
2021-12-21 11:53:32.729 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Cache-Hits: 0, 1
2021-12-21 11:53:32.729 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Per-Page: 10
2021-12-21 11:53:32.729 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Ratelimit-Limit: 50
2021-12-21 11:53:32.729 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Ratelimit-Remaining: 49
2021-12-21 11:53:32.729 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Request-Id: 6d958f7f-491a-42b9-8f48-47670b8c5836
2021-12-21 11:53:32.729 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Runtime: 0.086386
2021-12-21 11:53:32.730 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Served-By: cache-iad-kcgs7200028-IAD, cache-mad22057-MAD
2021-12-21 11:53:32.730 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Timer: S1640084013.381487,VS0,VE1
2021-12-21 11:53:32.730 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Total: 10000
2021-12-21 11:53:32.730 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - -> X-Unsplash-Version: v1
2021-12-21 11:53:32.887 7396-7451/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-2] INFO io.ktor.client.HttpClient - {"total":10000,"total_pages":1000,"results":[{"id":"ZCHj_2lJP00","created_at":"2020-06-15T00:30:27-04:00","updated_at":"2021-12-20T07:12:05-05:00","promoted_at":"2020-06-15T04:16:29-04:00","width":5304,"height":7952,"color":"#a6d9d9","blur_hash":"LRJcqDIUL3s..mX8rXRPOZnirWXT","description":null,"alt_description":"white and brown long fur cat","urls":{"raw":"https://images.unsplash.com/photo-1592194996308-7b43878e84a6?ixid=MnwyMjczNDJ8MHwxfHNlYXJjaHwxfHxjYXRzfGVufDB8fHx8MTY0MDA4MzU2OA\u0026ixlib=rb-1.2.1","full":"https://images.unsplash.com/photo-1592194996308-7b43878e84a6?crop=entropy\u0026cs=srgb\u0026fm=jpg\u0026ixid=MnwyMjczNDJ8MHwxfHNlYXJjaHwxfHxjYXRzfGVufDB8fHx8MTY0MDA4MzU2OA\u0026ixlib=rb-1.2.1\u0026q=85","regular":"https://images.unsplash.com/photo-1592194996308-7b43878e84a6?crop=entropy\u0026cs=tinysrgb\u0026fit=max\u0026fm=jpg\u0026ixid=MnwyMjczNDJ8MHwxfHNlYXJjaHwxfHxjYXRzfGVufDB8fHx8MTY0MDA4MzU2OA\u0026ixlib=rb-1.2.1\u0026q=80\u0026w=1080","small":"https://images.unsplash.com/photo-1592194996308-7b43878e84a6?crop=entropy\u0026cs=tinysrgb\u0026fit=max\u0026fm=jpg\u0026ixid=MnwyMjczNDJ8MHwxfHNlYXJjaHwxfHxjYXRzfGVufDB8fHx8MTY0MDA4MzU2OA\u0026ixlib=rb-1.2.1\u0026q=80\u0026w=400","thumb":"https://images.unsplash.com/photo-1592194996308-7b43878e84a6?crop=entropy\u0026cs=tinysrgb\u0026fit=max\u0026fm=jpg\u0026ixid=MnwyMjczNDJ8MHwxfHNlYXJjaHwxfHxjYXRzfGVufDB8fHx8MTY0MDA4MzU2OA\u0026ixlib=rb-1.2.1\u0026q=80\u0026w=200"},"links":{"self":"https://api.unsplash.com/photos/ZCHj_2lJP00","html":"https://unsplash.com/photos/ZCHj_2lJP00","download":"https://unsplash.com/photos/ZCHj_2lJP00/download?ixid=MnwyMjczNDJ8MHwxfHNlYXJjaHwxfHxjYXRzfGVufDB8fHx8MTY0MDA4MzU2OA","download_location":"https://api.unsplash.com/photos/ZCHj_2lJP00/download?ixid=MnwyMjczNDJ8MHwxfHNlYXJjaHwxfHxjYXRzfGVufDB8fHx8MTY0MDA4MzU2OA"},"categories":[],"likes":882,"liked_by_user":false,"current_user_collections":[],"sponsorship":null,"topic_submissions":{"animals":{"status":"approved","approved_on":"2020-06-16T07:38:49-04:00"},"wallpapers":{"status":"approved","approved_on":"2021-04-23T06:55:04-04:00"}},"user":{"id":"1LMzZNX562k","updated_at":"2021-12-21T05:20:13-05:00","username":"alvannee","name":"Alvan Nee","first_name":"Alvan","last_name":"Nee","twitter_username":"Alvan Nee","portfolio_url":null,"bio":"I really love unsplash!!!!!&quot;,"location":"Shanghai, China","links":{"self":"https://api.unsplash.com/users/alvannee","html":"https://unsplash.com/@alvannee","photos":"https://api.unsplash.com/users/alvannee/photos","likes":"https://api.unsplash.com/users/alvannee/likes","portfolio":"https://api.unsplash.com/users/alvannee/portfolio","following":"https://api.unsplash.com/users/alvannee/following","followers":"https://api.unsplash.com/users/alvannee/followers"},"profile_image":{"small":"https://images.unsplash.com/profile-1617947361627-4a8765a9b014image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=32\u0026w=32","medium":"https://images.unsplash.com/profile-1617947361627-4a8765a9b014image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=64\u0026w=64","large":"https://images.unsplash.com/profile-1617947361627-4a8765a9b014image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=128\u0026w=128"},"instagram_username":"alvan_nee","total_collections":0,"total_likes":68,"total_photos":191,"accepted_tos":true,"for_hire":false,"social":{"instagram_username":"alvan_nee","portfolio_url":null,"twitter_username":"Alvan Nee","paypal_email":null}},"tags":[{"type":"landing_page","title":"cat","source":{"ancestry":{"type":{"slug":"images","pretty_slug":"Images"},"category":{"slug":"animals","pretty_slug":"Animals"},"subcategory":{"slug":"cat","pretty_slug":"Cat"}},"title":"Cat Images \u0026 Pictures","subtitle":"Download free cat images","description":"9 lives isn't enough to capture the amazing-ness of cats. You need high-quality, professio
2021-12-21 11:53:32.887 7396-7451/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-2] INFO io.ktor.client.HttpClient - BODY END
2021-12-21 11:53:32.894 7396-7471/com.jshvarts.kmp.android W/System.err: [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient - RESPONSE https://api.unsplash.com/search/photos?query=cats&page=1&per_page=10 failed with exception: kotlinx.serialization.json.JsonDecodingException: Unexpected JSON token at offset 0: Expected '[, kind: LIST'.

我正在阅读一些论坛,可能与解码响应的需要有关。

正如我所说,我真的只是开始 which ktor 并且我不知道如何正确实施

提前感谢!

4

0 回答 0