【问题标题】:How can I check to see if JSON data is null without an infinite loop?如何在没有无限循环的情况下检查 JSON 数据是否为空?
【发布时间】:2020-09-22 21:59:54
【问题描述】:

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

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)
        }
    }
}

MarsPhoto 数据类

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

照片数据类


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

【问题讨论】:

    标签: android kotlin viewmodel


    【解决方案1】:

    这里有很多我能想到的潜在解决方案。但是,鉴于该应用需要具有可预测且合理的用户体验,因此我将首先确定问题的范围。

    • 由于每次都请求随机资源,因此它总是有可能为空。因此,无法取消(但可以减少)多次往返。
    • 多次 HTTP 往返,以及多次返回 null 的不可预测性,这可能会给用户体验带来极大的挫败感。

    以下是可以处理此问题的潜在方法(按复杂度递增的顺序)。

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

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

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

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

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

    专业提示:理想情况下(如果您正在构建产品/服务),移动应用应该是非常可预测的,并且必须注意用户的时间。因此,从这些资源请求数据的任务应该是后端服务器和数据库的任务,它们运行某种服务来获取和存储数据,然后应用程序会请求该服务器在这个子集中获取数据没有任何空值。

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

    【讨论】:

    • 这就是我来这里的答案。非常感谢您花时间输入所有这些并考虑解决方案。至于专业提示,我将开始学习设计后端服务器和数据库(我假设是 Spring)?从第二个解决方案开始,我会实现从我的视图模型中查询数据库的逻辑吗?
    • 非常感谢!是的,您可以选择任何后端或数据库技术,核心概念保持不变。
    • 我不建议从您的视图模型中查询。通常,您会希望尽可能地分离业务逻辑和数据层,以使代码更具可读性和可测试性。为此,您可以根据应用程序的需求和复杂性遵循 MVP、MVVM 或 MVI 等架构模式。这里有一些很棒的读物:blog.cleancoder.com/uncle-bob/2012/08/13/…antonioleiva.com/clean-architecture-android 另外,这里有一个使用 MVVM(一种非常常见的架构)的示例项目github.com/skydoves/Pokedex
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-30
    • 1970-01-01
    • 2018-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-28
    相关资源
    最近更新 更多