如何在没有无限循环的情况下检查 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.
视图模型
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 的不可预测性,用户体验确实令人沮丧。
以下是可以处理的潜在方法(按照复杂性递增的顺序)。
- 最简单的解决方案是在存储库级别实现逻辑,其中函数 getCuriosityPhotos 负责无限期地请求 api 资源,直到它以非空数据响应为止。这将解决用户最终会看到一些东西的核心问题(但这可能会花费很多时间)。
(PS- 您还需要将随机数生成委托为存储库可用的潜在服务。)
- 为了减少用户的请求数量 wait-time,您可以将请求参数和响应保存到 in-app 数据库中。因此,您的数据库可以充当单一的事实来源。因此,在发出请求之前,您可以查询数据库以检查该应用程序之前是否曾请求过相同的参数。如果没有,则派发其他请求,如果有,则无需再次请求,您可以使用之前的结果。如果为空,则重新生成另一个随机数并重试。如果它不为空,则从数据库提供数据。 (这是一个足够好的解决方案,随着越来越多的请求和响应被保存,用户等待时间会不断减少)
(注意:如果端点不响应静态数据并且数据不断变化,则更喜欢使用 in-memory 数据库而不是 SQLite 等持久性数据库)
- 该应用程序可以 运行 一个后台服务,该服务不断(通过遍历请求参数的所有可能组合)请求数据并将其保存到数据库中。当用户请求随机数据时,应用程序应显示数据库中的一组随机数据。如果数据库 empty/does 未达到数据库中至少有 n 行的阈值,应用程序可能会显示初始化设置 UI。
Pro-tip:理想情况下(如果您正在构建 product/service),移动应用程序应该非常非常可预测,并且必须注意用户的时间。因此,从此类资源请求数据的任务应该是后端服务器和数据库的任务,它们运行某种服务来获取和存储数据,in-turn 应用程序将请求该服务器从该子集中获取数据没有任何空值。
我是从解决不同粒度问题的角度来回答这个问题的。如果您在技术实施部分需要 help/advice,请告诉我,我们很乐意提供帮助!
我有一个视图模型和数据 classes 可以获取 NASA api 的火星照片。应该向用户显示来自查询的随机日期的图像。我总是需要一张图片 url(照片中的 imgSrc class)returned。如果没有找到url(imgSrc),则刷新数据直到找到并显示。此逻辑需要 return imgSrc 在应用程序启动后以及在 swiperefreshlayout 之后(如果用户选择滑动刷新)。我已经坚持了一个星期没有解决。处理这个问题的最佳方法是什么?即使我必须重构我的代码,我也希望有人指出正确的方向。
Here is the actual project on github.
视图模型
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 的不可预测性,用户体验确实令人沮丧。
以下是可以处理的潜在方法(按照复杂性递增的顺序)。
- 最简单的解决方案是在存储库级别实现逻辑,其中函数 getCuriosityPhotos 负责无限期地请求 api 资源,直到它以非空数据响应为止。这将解决用户最终会看到一些东西的核心问题(但这可能会花费很多时间)。
(PS- 您还需要将随机数生成委托为存储库可用的潜在服务。)
- 为了减少用户的请求数量 wait-time,您可以将请求参数和响应保存到 in-app 数据库中。因此,您的数据库可以充当单一的事实来源。因此,在发出请求之前,您可以查询数据库以检查该应用程序之前是否曾请求过相同的参数。如果没有,则派发其他请求,如果有,则无需再次请求,您可以使用之前的结果。如果为空,则重新生成另一个随机数并重试。如果它不为空,则从数据库提供数据。 (这是一个足够好的解决方案,随着越来越多的请求和响应被保存,用户等待时间会不断减少)
(注意:如果端点不响应静态数据并且数据不断变化,则更喜欢使用 in-memory 数据库而不是 SQLite 等持久性数据库)
- 该应用程序可以 运行 一个后台服务,该服务不断(通过遍历请求参数的所有可能组合)请求数据并将其保存到数据库中。当用户请求随机数据时,应用程序应显示数据库中的一组随机数据。如果数据库 empty/does 未达到数据库中至少有 n 行的阈值,应用程序可能会显示初始化设置 UI。
Pro-tip:理想情况下(如果您正在构建 product/service),移动应用程序应该非常非常可预测,并且必须注意用户的时间。因此,从此类资源请求数据的任务应该是后端服务器和数据库的任务,它们运行某种服务来获取和存储数据,in-turn 应用程序将请求该服务器从该子集中获取数据没有任何空值。
我是从解决不同粒度问题的角度来回答这个问题的。如果您在技术实施部分需要 help/advice,请告诉我,我们很乐意提供帮助!