android 为什么我不能从API获取数据?我的实时数据“测试”值等于空,怎么解决呢?

pod7payv  于 2023-05-27  发布在  Android
关注(0)|答案(1)|浏览(150)

为什么我不能从API获取数据?我的实时数据“测试”值等于空。怎么解决呢?
源代码:https://github.com/ElmarShkrv/ChioreRickAndMorty

分页来源:

class HomeFragmentPagingSource(
    private val status: String?,
    private val gender: String?,
    private val rickAndMortyApi: RickAndMortyApi,

) : PagingSource<Int, Characters>() {

    override fun getRefreshKey(state: PagingState<Int, Characters>): Int? {
        return null
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Characters> {
        val pageNumber = params.key ?: 1
        return try {
            val response = rickAndMortyApi.getAllCharacters(status, gender, pageNumber)
            val pagedResponse = response.body()
            val data = pagedResponse?.results

            var nextPageNumber: Int? = null
            if (pagedResponse?.info?.next != null) {
                val uri = Uri.parse(pagedResponse.info.next)
                val nextPageQuery = uri.getQueryParameter("page")
                nextPageNumber = nextPageQuery?.toInt()
            }

            LoadResult.Page(
                data = data.orEmpty(),
                prevKey = if (pageNumber == STARTING_PAGE_INDEX) null else pageNumber - 1,
                nextKey = nextPageNumber
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

仓库:

class HomeRepository @Inject constructor(
private val rickAndMortyApi: RickAndMortyApi
) {

    fun getAllCharacters() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                maxSize = 100,
                enablePlaceholders = false
            ),
            pagingSourceFactory = { HomeFragmentPagingSource(null, null, rickAndMortyApi) }
        ).liveData
    
    fun getCharactersbyStatusAndGender(status: String, gender: String) =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                maxSize = 100,
                enablePlaceholders = false
            ),
            pagingSourceFactory = { HomeFragmentPagingSource(status, gender, rickAndMortyApi) }
        ).liveData
    
    fun getCharactersByStatus(status: String) =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                maxSize = 100,
                enablePlaceholders = false
            ),
            pagingSourceFactory = { HomeFragmentPagingSource(status, null, rickAndMortyApi) }
        ).liveData
    
    fun getCharactersByGender(gender: String) =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                maxSize = 100,
                enablePlaceholders = false
            ),
            pagingSourceFactory = { HomeFragmentPagingSource(null, gender, rickAndMortyApi) }
        ).liveData

}

ViewModel:

@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: HomeRepository,
) : ViewModel() {

    private var _test = MutableLiveData<PagingData<Characters>>()
    val test: LiveData<PagingData<Characters>> = _test

//    var test = MutableLiveData\<PagingData\<Characters\>\>()

    var filterValue = MutableLiveData<Array<Int>>()
    
    init {
        filterValue.value = arrayOf(0, 0)
    }
    
    fun getAllCharacters(): LiveData<PagingData<Characters>> {
        val response = repository.getAllCharacters().cachedIn(viewModelScope)
            _test.value = response.value
        return response
    }
    
    fun getByStatusAndGender(status: String, gender: String): LiveData<PagingData<Characters>> {
        val response =
            repository.getCharactersbyStatusAndGender(status, gender).cachedIn(viewModelScope)
            _test.value = response.value
        return response
    }
    
    fun getByStatus(status: String): LiveData<PagingData<Characters>> {
        val response = repository.getCharactersByStatus(status).cachedIn(viewModelScope)
            _test.value = response.value
        return response
    }
    
    fun getByGender(gender: String): LiveData<PagingData<Characters>> {
        val response = repository.getCharactersByGender(gender).cachedIn(viewModelScope)
            _test.value = response.value
        return response
    }

FilterFragment:

@AndroidEntryPoint
class FilterFragment : BottomSheetDialogFragment() {

    private lateinit var binding: FragmentFilterBinding
    private val viewModel by viewModels<HomeViewModel>()
    
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        binding = FragmentFilterBinding.inflate(inflater)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
    
        binding.apply {
            viewModel.filterValue.observe(viewLifecycleOwner) { item ->
                chipgroupStatus.setChipChecked(item[0])
                radiogroupGender.setButtonChecked(item[1])
            }
        }
    
        binding.apply {
            btnMakeFilter.setOnClickListener {
    
                if (chipgroupStatus.getTextChipChecked()
                        .isNotEmpty() && radiogroupGender.getTextButtonChecked().isNotEmpty()
                ) {
                    viewModel.getByStatusAndGender(
                        chipgroupStatus.getTextChipChecked(),
                        radiogroupGender.getTextButtonChecked(),
                    )
                } else {
                    if (chipgroupStatus.getTextChipChecked().isNotEmpty()) {
                        viewModel.getByStatus(chipgroupStatus.getTextChipChecked())
                    } else {
                        viewModel.getByGender(radiogroupGender.getTextButtonChecked())
                    }
                }
    
                viewModel.filterValue.value = arrayOf(
                    chipgroupStatus.checkedChipId, radiogroupGender.checkedRadioButtonId
                )
    
                findNavController().popBackStack(R.id.homeFragment, false)
            }
        }
    
    }

}

HomeFragment:(我想展示数据)

@AndroidEntryPoint
class HomeFragment : Fragment() {

    private lateinit var binding: FragmentHomeBinding
    private lateinit var homeRvAdapter: HomeRvAdapter
    private val viewModel by viewModels<HomeViewModel>()
    private val TAG = "Home"
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        binding = FragmentHomeBinding.inflate(inflater)
        return binding.root
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
    
        viewModel.getAllCharacters()
    
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
    
        setUpHomeRv()
        initAdapter()
        observeFilteredData()
    
        binding.filterIv.setOnClickListener {
            findNavController().navigate(R.id.action_homeFragment_to_filterFragment)
        }
    
    }
    
    
    private fun observeFilteredData() {
        viewModel.test.observe(viewLifecycleOwner) { filteredData ->
            if (filteredData == null) {
                Log.e(TAG, "filterdata equal null")
            }
            filteredData?.let {
                homeRvAdapter.submitData(lifecycle, it)
            }
        }
    }
    
    private fun initAdapter() {
    
        binding.homeRv.adapter = homeRvAdapter.withLoadStateHeaderAndFooter(
            header = HomeLoadStateAdapter { homeRvAdapter.retry() },
            footer = HomeLoadStateAdapter { homeRvAdapter.retry() },
        )
    
        homeRvAdapter.addLoadStateListener { loadState ->
            binding.homeRv.isVisible = loadState.source.refresh is LoadState.NotLoading
            binding.shimmerLayout.isVisible = loadState.source.refresh is LoadState.Loading
            binding.tvHomeSearch.isInvisible = loadState.source.refresh is LoadState.Loading
            binding.filterIv.isInvisible = loadState.source.refresh is LoadState.Loading
            binding.retryBtn.isVisible = loadState.source.refresh is LoadState.Error
            handleError(loadState)
        }
    
        binding.retryBtn.setOnClickListener {
            homeRvAdapter.retry()
        }
    }
    
    private fun handleError(loadState: CombinedLoadStates) {
        val errorStates = loadState.source.append as? LoadState.Error
            ?: loadState.source.prepend as? LoadState.Error
    
        errorStates?.let {
            Toast.makeText(requireContext(), "${it.error}", Toast.LENGTH_LONG).show()
        }
    
    }
    
    private fun setUpHomeRv() {
        homeRvAdapter = HomeRvAdapter()
        binding.apply {
            homeRv.adapter = homeRvAdapter
    
            homeRvAdapter.stateRestorationPolicy =
                RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
    
            homeRv.addItemDecoration(
                DefaultItemDecorator(
                    resources.getDimensionPixelSize(R.dimen.horizontal_margin),
                    resources.getDimensionPixelSize(R.dimen.vertical_margin)
                )
            )
        }
    }

}

当我像这样更改视图模型时:

@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: HomeRepository,
) : ViewModel() {

    var test = MutableLiveData<PagingData<Characters>>()
    
    var filterValue = MutableLiveData<Array<Int>>()
    
    init {
        filterValue.value = arrayOf(0, 0)
    }
    
    fun getAllCharacters(): LiveData<PagingData<Characters>> {
        val response = repository.getAllCharacters().cachedIn(viewModelScope)
        test = response as MutableLiveData<PagingData<Characters>>
        return response
    }
    
    fun getByStatusAndGender(status: String, gender: String): LiveData<PagingData<Characters>> {
        val response =
            repository.getCharactersbyStatusAndGender(status, gender).cachedIn(viewModelScope)
        test = response as MutableLiveData<PagingData<Characters>>
        return response
    }
    
    fun getByStatus(status: String): LiveData<PagingData<Characters>> {
        val response = repository.getCharactersByStatus(status).cachedIn(viewModelScope)
        test = response as MutableLiveData<PagingData<Characters>>
        return response
    }
    
    fun getByGender(gender: String): LiveData<PagingData<Characters>> {
        val response = repository.getCharactersByGender(gender).cachedIn(viewModelScope)
        test = response as MutableLiveData<PagingData<Characters>>
        return response
    }

它工作了,但我不能过滤数据,为什么我以这种方式获得数据,但以前的方式我得到null

6tqwzwtp

6tqwzwtp1#

第二种方法不进行过滤,因为当调用过滤函数时,您将test中保存的LiveData对象(您正在观察的对象)替换为一个新对象(您没有观察到的对象)。所以你的观察者永远看不到更新。
我没有使用过 paging 库,但我假设第一种方法失败了,因为分页是异步发生的。因此,当您第一次从存储库接收到LiveData时,其中没有任何内容(结果页面尚未获取),因此其valuenull。这就是你设置的test的值。结果应该在某个时候到达寻呼机LiveData,但是您已经丢弃了它--您没有用新值更新test
有很多方法可以解决这个问题(我觉得协程是最简单的),但是既然你已经在使用LiveData,你可以尝试使用switchMap,它允许你创建一个LiveData,它可以输出你在其他LiveData对象之间切换的结果。
这是通过使用LiveData作为您的 * 控件 * 来实现的-当该控件的值更改时,switchMap调用一个返回LiveDatasource 的函数。因此,基本上,您需要一个控件来保存 * 当前过滤类型 *,并且该函数需要 * 与repo对话以获取寻呼机LiveData
因为这不是您的ViewModel代码当前的工作方式,所以您可能需要稍微重写一下。首先让我们得到一个过滤数据对象:

sealed class DataFilter {
    object All : DataFilter()
    data class StatusAndGender(val status: String, gender: String) : DataFilter()
    data class Status(val status: String) : DataFilter()
    data class Gender(val gender: String) : DataFilter()
}

因此,我们定义了所有可能的过滤器类型,以及每个类型所包含的数据。(对于你正在做的事情,你不需要这个,你可以使用一个带有可空statusgender的数据类,并根据提供的数据来处理它,但这是一个更通用的方法,适用于任何情况)
然后你会有一个私有的LiveData来保存当前的过滤器类型,还有一个公共函数来设置它:

// in the VM again
private _filter = MutableLiveData<DataFilter>() // you could add a default value if you want

fun setFilter(filter: DataFilter) {
    _filter.value = filter
}

然后你把test连接起来,作为对_filter中的变化做出React的东西:

val test = Transformations.switchMap(_filter) { filter ->
    when(filter) {
        is DataFilter.Status -> repository.getCharactersByStatus(filter.status)
        is DataFilter.StatusAndGender -> repository.getCharactersByStatusAndGender(filter.status, filter.gender)
        is DataFilter.Gender -> repository.getCharactersByGender(filter.gender)
        DataFilter.All -> repository.getAllCharacters()
    }.cachedIn(viewModelScope)
}

这基本上应该做到了。您可以通过调用例如setFilter(DataFilter.Status(whatever))switchMap通过调用适当的repo函数对更改做出React,并使用返回的LiveData作为test的新值源。由于test本身从未被重新分配,因此无论观察它的是什么,它都会看到所有分页的值作为过滤更改而被输入。

相关问题