改造响应为 null,但 okHttp Interceptor 记录器有数据

Retrofit response is null but okHttp Interceptor logger has data

提问人:Dean Ball 提问时间:11/6/2023 更新时间:11/6/2023 访问量:48

问:

我有一个 POST 请求,要根据生成的 SessionKey 获取数据。如果 SessionKey 有效,则会响应某些 Customer 数据。

我面临的问题是我的 Retrofit 实例给了我空响应,但 okHttp Interceptor 记录器正在返回数据,如 Logcat 中所示。

我显然在某个地方出了问题,谁能给我指出正确的方向?希望这很简单!

API结构

<OrderInfoResponse
  <OrderInfoResult>
    <deliveryAddresses>
      <OrderingDeliveryAddressStruct>
        <deliveryAddressNo>string</deliveryAddressNo>
        <deliveryAddressName>string</deliveryAddressName>
        <pointsOfService>
          <OrderingPointOfServiceStruct>
            <pointOfServiceNo>string</pointOfServiceNo>
            <pointOfServiceName>string</pointOfServiceName>
            <pointOfServiceDescription>string</pointOfServiceDescription>
            <pointOfServiceOrderingGroupNo>string</pointOfServiceOrderingGroupNo>
            <orders>
              <OrderingOrderStruct>
                <orderType>string</orderType>
                <orderDate>date</orderDate>
                <deliveryDate>date</deliveryDate>
                <orderStatus>int</orderStatus>
                <articles>
                  <OrderingArticleStruct>
                    <articleNo>string</articleNo>
                    <articleDescription>string</articleDescription>
                    <articleSize>string</articleSize>
                    <articleTargetQty>int</articleTargetQty>
                    <articleMinQty>int</articleMinQty>
                    <articleMaxQty>int</articleMaxQty>
                    <articleIntervalQty>int</articleIntervalQty>
                  </OrderingArticleStruct>
                </articles>
              </OrderingOrderStruct>                  
          </OrderingPointOfServiceStruct>              
        </pointsOfService>
      </OrderingDeliveryAddressStruct>
    </deliveryAddresses>
    <orderingGroups>
      <OrderingOrderingGroupStruct>
        <orderingGroupNo>string</orderingGroupNo>
        <orderingGroupDescription>string</orderingGroupDescription>
      </OrderingOrderingGroupStruct>
      <OrderingOrderingGroupStruct>
        <orderingGroupNo>string</orderingGroupNo>
        <orderingGroupDescription>string</orderingGroupDescription>
      </OrderingOrderingGroupStruct>
    </orderingGroups>
  </OrderInfoResult>
</OrderInfoResponse>

示例 API 响应

{
"deliveryAddresses": {
"OrderingDeliveryAddressStruct": {
  "deliveryAddressNo": 5710030,
  "deliveryAddressName": "UCLH WARDS LINEN",
  "pointsOfService": {
    "OrderingPointOfServiceStruct": {
      "pointOfServiceNo": 3,
      "pointOfServiceName": "E02 S1 THE",
      "pointOfServiceDescription": "E02 STORE 1 THEATRES / LABOUR",
      "pointOfServiceOrderingGroupNo": "571-1",
      "orders": {
        "OrderingOrderStruct": {
          "orderType": "inventory",
          "orderDate": "2023-11-02",
          "deliveryDate": "2023-11-02",
          "orderStatus": 0,
          "articles": {
            "OrderingArticleStruct": {
              "articleNo": 109842,
              "articleDescription": "ESSENTIALS TOP SHEET S",
              "articleSize": "",
              "articleTargetQty": 80,
              "articleMinQty": 0,
              "articleMaxQty": 0,
              "articleIntervalQty": 0
            }
          }
        }
      }
    }
  }
}
},
"orderingGroups": {
 "OrderingOrderingGroupStruct": {
   "orderingGroupNo": "571-1",
   "orderingGroupDescription": "UCLH"
 }
}
}

数据类

import com.google.gson.annotations.SerializedName

data class OrderInfo (

  @SerializedName("OrderingOrderInfoResponseStruct" ) var OrderingOrderInfoResponseStruct : 
  OrderingOrderInfoResponseStruct? = OrderingOrderInfoResponseStruct()
)

data class OrderingOrderInfoResponseStruct (
    
  @SerializedName("deliveryAddresses" ) var deliveryAddresses : DeliveryAddresses? = 
  DeliveryAddresses(),
  @SerializedName("orderingGroups"    ) var orderingGroups    : OrderingGroups?    = 
  OrderingGroups()
)

data class DeliveryAddresses (

  @SerializedName("OrderingDeliveryAddressStruct" ) var OrderingDeliveryAddressStruct : 
  OrderingDeliveryAddressStruct? = OrderingDeliveryAddressStruct()    
)

data class OrderingGroups (

  @SerializedName("OrderingOrderingGroupStruct" ) var OrderingOrderingGroupStruct : 
  OrderingOrderingGroupStruct? = OrderingOrderingGroupStruct()    
)

data class OrderingDeliveryAddressStruct (

  @SerializedName("deliveryAddressNo"   ) var deliveryAddressNo   : Int?             = null,
  @SerializedName("deliveryAddressName" ) var deliveryAddressName : String?          = null,
  @SerializedName("pointsOfService"     ) var pointsOfService     : PointsOfService? = 
  PointsOfService()
)

data class OrderingOrderingGroupStruct (

  @SerializedName("orderingGroupNo"          ) var orderingGroupNo          : String? = null,
  @SerializedName("orderingGroupDescription" ) var orderingGroupDescription : String? = null
)

data class OrderingPointOfServiceStruct (

  @SerializedName("pointOfServiceNo"              ) var pointOfServiceNo              : Int?    = 
  null,
  @SerializedName("pointOfServiceName"            ) var pointOfServiceName            : String? = 
  null,
  @SerializedName("pointOfServiceDescription"     ) var pointOfServiceDescription     : String? = 
  null,
  @SerializedName("pointOfServiceOrderingGroupNo" ) var pointOfServiceOrderingGroupNo : String? = 
  null,
  @SerializedName("orders"                        ) var orders                        : Orders? = 
  Orders()
)

data class Orders (

  @SerializedName("OrderingOrderStruct" ) var OrderingOrderStruct : OrderingOrderStruct? = 
  OrderingOrderStruct()
)

data class OrderingOrderStruct (

  @SerializedName("orderType"    ) var orderType    : String?   = null,
  @SerializedName("orderDate"    ) var orderDate    : String?   = null,
  @SerializedName("deliveryDate" ) var deliveryDate : String?   = null,
  @SerializedName("orderStatus"  ) var orderStatus  : Int?      = null,
  @SerializedName("articles"     ) var articles     : Articles? = Articles()    
)

data class Articles (

  @SerializedName("OrderingArticleStruct" ) var OrderingArticleStruct : OrderingArticleStruct? = 
  OrderingArticleStruct()    
)

data class OrderingArticleStruct (

  @SerializedName("articleNo"          ) var articleNo          : Int?    = null,
  @SerializedName("articleDescription" ) var articleDescription : String? = null,
  @SerializedName("articleSize"        ) var articleSize        : String? = null,
  @SerializedName("articleTargetQty"   ) var articleTargetQty   : Int?    = null,
  @SerializedName("articleMinQty"      ) var articleMinQty      : Int?    = null,
  @SerializedName("articleMaxQty"      ) var articleMaxQty      : Int?    = null,
  @SerializedName("articleIntervalQty" ) var articleIntervalQty : Int?    = null
)

接口

interface OrderInfoInterface {
        @Headers("Content-Type: application/json; charset=utf-8")
        @POST("OrderInfo/")
        suspend fun getOrderInfo(@Body orderInfoRequest: OrderingRequest):
                Response<OrderInfo>

        companion object{
            fun getApi(): OrderInfoInterface? {
                return ApiClient.client?.create(OrderInfoInterface::class.java)
            }
        }
}

Api 客户端

object ApiClient {
    private val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

    private val logger = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)

    private val mOkHttpClient = OkHttpClient.Builder()
        .addInterceptor(logger)
        .connectTimeout(Duration.ZERO)
        .writeTimeout(5, TimeUnit.MINUTES)
        .readTimeout(5, TimeUnit.MINUTES)
        .build()

    var mRetrofit: Retrofit? = null

    val client: Retrofit?
        get() {
            if (mRetrofit == null) {
                mRetrofit = Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(mOkHttpClient)
                    //.addConverterFactory(GsonConverterFactory.create(gson))
                    .addConverterFactory(MoshiConverterFactory.create(moshi))
                    .build()
            }
            return mRetrofit
        }
}

存储 库

class DeliveryAddressRepository{

    suspend fun getOrderInfoData(sessionKey: OrderingRequest): Response<OrderInfo>?{
        return OrderInfoInterface.getApi()?.getOrderInfo(sessionKey)
    }
}

视图模型

class LoginViewModel(application: Application) : AndroidViewModel(application) {

    private val _sessionKey = MutableLiveData<String?>()
    private val _message = MutableLiveData<String?>()
    private val userRepo = DeliveryAddressRepository()
    val sessionKey: MutableLiveData<String?>
        get() = _sessionKey
    val message: MutableLiveData<String?>
        get() = _message

    val loginResult: MutableLiveData<BaseResponse<OrderInfo>> = MutableLiveData()

    fun getOrderingData(orderSessionKey: OrderingRequest) {
        viewModelScope.launch {
            try {

                val response = userRepo.getOrderInfoData(orderSessionKey)
                if (response?.code() == 200) {
                    loginResult.value = BaseResponse.Success(response.body())
                } else {
                    loginResult.value = BaseResponse.Error(response?.message())
                }
            } catch (ex: Exception) {
                loginResult.value = BaseResponse.Error(ex.message)
            }
        }
    }

    fun getSessionKey(loginRequest: LoginRequest) {
        viewModelScope.launch {
            val response = LoginApi.retrofitService.getSessionKey(
                LoginRequest(loginRequest.username, loginRequest.password)
            )
            _sessionKey.value = response.sessionKey
            _message.value = response.message
        }
    }

    fun getDate(): String? {
        val formatter = DateTimeFormatter.ofPattern("EEEE, MMMM, dd, yyyy")
        return LocalDateTime.now().format(formatter)
    }
}

我的 Fragment 中的 ViewModel 调用

class LoginFragment : Fragment() {

    private lateinit var binding: FragmentLoginBinding
    private val sharedViewModel: ParamsViewModel by activityViewModels()

    private val loginViewModel: LoginViewModel by lazy {
        ViewModelProvider(this)[LoginViewModel::class.java]
    }

    private var username: Editable? = null
    private var password: Editable? = null
    private var sessionKey = ""

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false)
        val view = binding.root
        sharedViewModel.setAppVersion(BuildConfig.VERSION_NAME)
        sharedViewModel.setFlavor(BuildConfig.FLAVOR)
        binding.apply { viewModel = loginViewModel }
        binding.apply { paramViewModel = sharedViewModel }
        binding.lifecycleOwner = this
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // sets Today's date for login activity
        binding.date.text = loginViewModel.getDate()
        sharedViewModel.setOrderDate(binding.date.text.toString())
        sharedViewModel.setAppVersion(BuildConfig.VERSION_NAME)
        sharedViewModel.setFlavor(BuildConfig.FLAVOR)
        // sets Flavor banner details for login activity
        setFlavorBanner()
        // initiates Firebase remote config options
        fireBaseRemoteConfig()

        with(binding) {
            loginButton.setOnClickListener {
                view
                if (checkUsernamePassword()) {
                    val login = LoginRequest(username.text.toString(), password.text.toString())
                    loginViewModel.getSessionKey(loginRequest = login)
                    val orderInfoRequest = OrderingRequest()
                    loginViewModel.getOrderingData(orderInfoRequest)
                    binding.lifecycleOwner?.let { it1 ->
                        loginViewModel.loginResult.observe(it1) {
                            when (it) {
                                is BaseResponse.Loading -> {
                                    Toast.makeText(
                                        activity,
                                        "Loading Data",
                                        Toast.LENGTH_LONG
                                    ).show()
                                }

                                is BaseResponse.Success -> {
                                    Toast.makeText(
                                        activity,                                            
                                        it.data?.OrderingOrderInfoResponseStruct.toString(),
                                        Toast.LENGTH_LONG
                                    ).show()
                                    findNavController().navigate(R.id.action_loginFragment_to_landingPageFragment)
                                }

                                is BaseResponse.Error -> {
                                    Toast.makeText(
                                        activity,
                                        it.msg,
                                        Toast.LENGTH_LONG
                                    ).show()
                                }
                            }
                        }   


    private fun setFlavorBanner() {
        val flavorBanner = binding.flavorBanner
        // sets banner text
        if (sharedViewModel.flavor.value == "dev") {
            flavorBanner.text = resources.getString(R.string.devFlavorText)
        }
        // hides banner if PROD application
        if (sharedViewModel.flavor.value == "prod") {
            flavorBanner.isVisible = false;
        }
        // sets banner text and banner color
        if (sharedViewModel.flavor.value == "preProd") {
            flavorBanner.text = resources.getString(R.string.testFlavorText)
            flavorBanner.run {
                setBackgroundColor(
                    ContextCompat.getColor(
                        context,
                        R.color.elis_orange
                    )
                )
            }
        }
    }

    private fun fireBaseRemoteConfig() {
        // sets the Firebase Remote Config settings
        val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig
        val configSettings = remoteConfigSettings {
            minimumFetchIntervalInSeconds = 3600
        }
        remoteConfig.setConfigSettingsAsync(configSettings)
        remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
        // Fetches remote config parameters setup in the Firebase console.
        remoteConfig.fetchAndActivate()
    }

    private fun checkUsernamePassword(): Boolean {

        username = binding.username.text
        password = binding.password.text

        return if (TextUtils.isEmpty(binding.username.text) ||
            TextUtils.isEmpty(binding.password.text)
        ) {
            Toast.makeText(activity, "Username or Password cannot be empty", Toast.LENGTH_LONG)
                .show();
            false
        } else
            true
    }

Logcat 条目 enter image description here

Android Kotlin 改造 2 OKHTTP

评论

0赞 V-master 11/7/2023
看起来您使用了错误的模型作为响应根,您应该使用而不是 .你不会收到任何错误,因为你的所有数据都可以为空,而moshi没有找到你想要的名字,并丢弃了与你的模型不匹配的条目。如果你确定某些数据必须作为响应,那么最好使用不可为空的值,在这种情况下让 moshi 崩溃(或者像这样使用它来测试,以便在无法正确解析响应时查看崩溃日志)OrderingOrderInfoResponseStructOrderInfo
0赞 Dean Ball 11/7/2023
@V师傅感谢您的快速回复!我之前确实尝试过更改它并返回以下错误“预期BEGIN_OBJECT但在路径 $.deliveryAddresses 处BEGIN_ARRAY”
0赞 Mahi 11/7/2023
这意味着它试图解析的位置 - 他们期望一个 json 对象,但得到了一个数组或 json 数组。检查您的回复。
1赞 V-master 11/7/2023
错误说:当尝试解析字段时,它得到了数组([方括号])而不是对象({大括号})。正如您在屏幕截图中看到的那样,您附加的响应与您作为文本附加到问题的响应不同,您需要对模型进行更正。数组可以解析为对象deliveryAddressesjava.util.List
1赞 V-master 11/7/2023
顺便说一句。您正在使用的是来自 gson 库的,不适用于 moshi - moshi 将按原样使用变量名称。如果您需要解析不同名称下的变量,则应使用@Json注解@SerializedName

答: 暂无答案