如何在没有无限循环的情况下检查 JSON 数据是否为空?

How can I check to see if JSON data is null without an infinite loop?

我有一个视图模型和数据 classes 可以获取 NASA api 的火星照片。应该向用户显示来自查询的随机日期的图像。我总是需要一张图片 url(照片中的 imgSrc class)returned。如果没有找到url(imgSrc),则刷新数据直到找到并显示。此逻辑需要 return imgSrc 在应用程序启动后以及在 swiperefreshlayout 之后(如果用户选择滑动刷新)。我已经坚持了一个星期没有解决。处理这个问题的最佳方法是什么?即使我必须重构我的代码,我也希望有人指出正确的方向。

Here is the actual project on github.

JSON that I want to fetch

JSON returning no imgSrc

视图模型

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dev20.themarsroll.models.MarsPhotos
import com.dev20.themarsroll.models.Photo
import com.dev20.themarsroll.repository.MarsPhotoRepository
import com.dev20.themarsroll.util.Resource
import kotlinx.coroutines.launch
import retrofit2.Response

class MarsPhotoViewModel(

    private val marsPhotoRepository: MarsPhotoRepository
    ): ViewModel() {

    val marsPhotos: MutableLiveData<Resource<MarsPhotos>> = MutableLiveData()

    init {
        getRandomPhotos()
    }

     fun getCuriosityPhotos(solQuery: Int, roverQuery: Int, camera: String) = viewModelScope.launch {
        marsPhotos.postValue(Resource.Loading())
        val response = marsPhotoRepository.getCuriosityPhotos(solQuery, roverQuery, camera)
        marsPhotos.postValue(handlePhotosResponse(response))
    }

    private fun handlePhotosResponse(response: Response<MarsPhotos> ) : Resource<MarsPhotos> {
        if(response.isSuccessful) {
                response.body()?.let { resultResponse ->
                    return Resource.Success(resultResponse)
            }
        }
        return Resource.Error(response.message())
    }

    fun getRandomPhotos() {
        getCuriosityPhotos((1..2878).random(), 5, "NAVCAM")
    }

    fun savePhoto(photo: Photo) = viewModelScope.launch {
        marsPhotoRepository.upsert(photo)
    }

    fun getSavedPhotos() = marsPhotoRepository.getSavedPhotos()

    fun deletePhoto(photo: Photo) = viewModelScope.launch {
        marsPhotoRepository.deletePhoto(photo)
    }
}

好奇片段


import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.View
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.dev20.themarsroll.R
import com.dev20.themarsroll.adapters.MarsPhotoAdapter
import com.dev20.themarsroll.util.Resource
import com.dev20.ui.MarsActivity
import com.dev20.ui.MarsPhotoViewModel
import kotlinx.android.synthetic.main.fragment_curiosity.*


class CuriosityFragment : Fragment(R.layout.fragment_curiosity) {
    lateinit var viewModel: MarsPhotoViewModel
    lateinit var marsPhotoAdapter: MarsPhotoAdapter
    
    val TAG = "CuriosityFragment"

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = (activity as MarsActivity).viewModel
        setupRecyclerView()

        swipeLayout.setOnRefreshListener {
            viewModel.getRandomPhotos()
            swipeLayout.isRefreshing = false
        }

        marsPhotoAdapter.setOnItemClickListener {
            val bundle = Bundle().apply {
                putSerializable("photo", it)
            }
            findNavController().navigate(
                R.id.action_curiosityFragment_to_cameraFragment,
                bundle
            )
        }

        viewModel.marsPhotos.observe(viewLifecycleOwner, { response ->
            when(response) {
                is Resource.Success -> {
                    hideProgressBar()
                    response.data?.let { curiosityResponse ->
                    marsPhotoAdapter.differ.submitList(curiosityResponse.photos)
                    }
                }
                is Resource.Error -> {
                    hideProgressBar()
                    response.message?.let { message ->
                        Log.e(TAG, "An Error occurred: $message")
                    }
                }
                is Resource.Loading -> {
                    showProgressBar()
                }
            }
        })
    }

    private fun hideProgressBar() {
        curiosityPaginationProgressBar.visibility = View.INVISIBLE
    }

    private fun showProgressBar() {
        curiosityPaginationProgressBar.visibility = View.VISIBLE
    }

    private fun setupRecyclerView() {
        marsPhotoAdapter = MarsPhotoAdapter()
        rvCuriosityPhotos.apply {
        adapter = marsPhotoAdapter
            layoutManager = LinearLayoutManager(activity)
        }
    }
}

火星照片数据class

data class MarsPhotos(
    val photos: MutableList<Photo>,
    val camera: MutableList<Camera>
)

照片数据class


import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.google.gson.annotations.SerializedName
import java.io.Serializable

@Entity(
    tableName = "photos"
)

@TypeConverters
data class Photo(
    @PrimaryKey(autoGenerate = true)
    var id: Int? = null,
    @SerializedName("earth_date")
    val earthDate: String,
    @SerializedName("img_src")
    val imgSrc: String,
    val sol: Int,
    @SerializedName("rover_id")
    val rover: Int,
) : Serializable

这里有很多我能想到的潜在解决方案。但是,考虑到应用程序需要具有可预测和合理的用户体验,在此我首先确定问题范围。

  • 由于每次请求的都是随机资源,所以它总是有可能为空。因此,多个round-trips不能取消(但可以减少)。
  • 多个 HTTP round-trips,加上多次返回 null 的不可预测性,用户体验确实令人沮丧。

以下是可以处理的潜在方法(按照复杂性递增的顺序)。

  1. 最简单的解决方案是在存储库级别实现逻辑,其中函数 getCuriosityPhotos 负责无限期地请求 api 资源,直到它以非空数据响应为止。这将解决用户最终会看到一些东西的核心问题(但这可能会花费很多时间)。

(PS- 您还需要将随机数生成委托为存储库可用的潜在服务。)

  1. 为了减少用户的请求数量 wait-time,您可以将请求参数和响应保存到 in-app 数据库中。因此,您的数据库可以充当单一的事实来源。因此,在发出请求之前,您可以查询数据库以检查该应用程序之前是否曾请求过相同的参数。如果没有,则派发其他请求,如果有,则无需再次请求,您可以使用之前的结果。如果为空,则重新生成另一个随机数并重试。如果它不为空,则从数据库提供数据。 (这是一个足够好的解决方案,随着越来越多的请求和响应被保存,用户等待时间会不断减少)

(注意:如果端点不响应静态数据并且数据不断变化,则更喜欢使用 in-memory 数据库而不是 SQLite 等持久性数据库)

  1. 该应用程序可以 运行 一个后台服务,该服务不断(通过遍历请求参数的所有可能组合)请求数据并将其保存到数据库中。当用户请求随机数据时,应用程序应显示数据库中的一组随机数据。如果数据库 empty/does 未达到数据库中至少有 n 行的阈值,应用程序可能会显示初始化设置 UI。

Pro-tip:理想情况下(如果您正在构建 product/service),移动应用程序应该非常非常可预测,并且必须注意用户的时间。因此,从此类资源请求数据的任务应该是后端服务器和数据库的任务,它们运行某种服务来获取和存储数据,in-turn 应用程序将请求该服务器从该子集中获取数据没有任何空值。

我是从解决不同粒度问题的角度来回答这个问题的。如果您在技术实施部分需要 help/advice,请告诉我,我们很乐意提供帮助!