【问题标题】:Retrofit @Patch method (Updating an object, which contains an ArrayList)改造 @Patch 方法(更新一个对象,其中包含一个 ArrayList)
【发布时间】:2020-01-21 21:09:25
【问题描述】:

注意:这里是新手,如果我需要提供更多信息或澄清任何事情,请告诉我。

为您提供一些背景信息:我正在练习使用许多 Retrofit 方法构建一个 Messenger 克隆应用程序。为此,我使用了一个小型本地 JSON 服务器,应用程序与之通信。

当应用程序的用户创建帐户时,应用程序使用以下方法在 JSON 服务器中创建配置文件对象:

@FormUrlEncoded
@POST("profiles")
suspend fun createProfile(@Field("username") username: String?,
                            @Field("picture") picture: String?,
                            @Field(value = "nickname") nickname: String?,
                            @Field(value = "contacts") contacts: ArrayList<String?>,
                            @Field(value = "status") status: Int?): Response<Profile>

最初,contacts ArrayList 是空的,因为用户还没有添加任何联系人。为联系人参数创建一个带有空 ArrayList() 的随机配置文件,这是 JSON 服务器内部的结果:

{
    "username": "username.example",
    "picture": "picture's URL",
    "nickname": "Nikola",
    "status": 1,
    "id": 4
}

在应用程序中代表 Profile 模型的类是这样的:

class Profile(
    val username: String? = "",
    var picture: String? = "",
    var nickname: String? = "",
    var contacts: ArrayList<String?>? = ArrayList(),
    var status: Int? = 1,
    val id: Int? = 0
)

创建个人资料后,用户自然可以添加新联系人,使用以下方法:

@FormUrlEncoded
@PATCH("profiles/{id}")
suspend fun addContact(@Path("id") id: Int?,
                        @Field("contacts") contacts: ArrayList<String?>?): Response<Profile>

这就是问题发生的地方,在添加的第一个联系人中。发送到服务器的 ArrayList 仅包含一项,JSON 服务器中的结果如下所示:

{
    "username": "username.example",
    "picture": "picture's URL",
    "nickname": "Nikola",
    "status": 1,
    "id": 4,
    "contacts": "first.contact"
}

基本上,因为arraylist 只包含一项,所以它将它保存为一个字符串。这会在以后产生各种问题,因为一旦应用程序对该配置文件使用了@GET 方法,它就需要一个 ArrayList 作为联系人属性,但它会收到一个字符串。

如何使 JSON 配置文件如下所示:

{
    "username": "username.example",
    "picture": "picture's URL",
    "nickname": "Nikola",
    "status": 1,
    "id": 4,
    "contacts": ["first.contact"]
}

contacts 参数必须是一个数组,即使其中只有一项。

【问题讨论】:

    标签: android json kotlin retrofit


    【解决方案1】:

    使用@Body 代替@Form@FormUrlEncoded

    data class ProfileContacts(val contacts: List<String>)
    
    @PATCH("profiles/{id}")
    suspend fun addContact(@Path("id") id: Int?, @Body contacts: ProfileContacts): Response<Profile>
    

    并添加一个转换器,如果您还没有转换器,例如 Gson 一个:

    // build.gradle
    dependencies {
        implementation 'com.squareup.retrofit2:converter-gson:2.6.1' // latest version
    }
    
    // Retrofit Builder
    val retrofit = Retrofit.Builder()
        ... // other methods
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    

    @Body 允许您将请求主体定义为 Kotlin 类,最终将使用提供的 Converter 进行序列化(对于 Gson,它将被转换为 JSON)。另一方面,@Field 用于以application/x-www-form-urlencoded 的形式发送数据(正如所需的@FormUrlEncoded 注释还建议的那样)。这意味着您的请求正文将被编码为键值对列表,以“&”分隔,例如(基于createProfile方法):

    username=username.example&picture=picture%27s%20URL&nickname=Nikola&status=1&id=4
    

    您可以通过多次使用同一个键将数组作为application/x-www-form-urlencoded 发布。当您使用 Retrofit @Field 注释对列表进行注释时,基本上会发生这种情况 - 列表中的每个元素都与公共键配对,例如:

    @FormUrlEncoded
    @PATCH("profiles/{id}")
    suspend fun addContact(@Path("id") id: Int?,
                           @Field("contacts") contacts: ArrayList<String?>?): Response<Profile>
    
    // ...
    addContact(1, arrayListOf("first.contact", "second.contact"))
    
    // request body:
    contacts=first.contact&contacts=second.contact
    

    因此,当您尝试仅使用一个元素 contacts 列表更新配置文件时,会创建一个“联系人”对 (contacts=first.contact),并将其视为字符串值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 2016-08-03
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 2015-08-11
      相关资源
      最近更新 更多