为什么我不能从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
1条答案
按热度按时间6tqwzwtp1#
第二种方法不进行过滤,因为当调用过滤函数时,您将
test
中保存的LiveData
对象(您正在观察的对象)替换为一个新对象(您没有观察到的对象)。所以你的观察者永远看不到更新。我没有使用过 paging 库,但我假设第一种方法失败了,因为分页是异步发生的。因此,当您第一次从存储库接收到
LiveData
时,其中没有任何内容(结果页面尚未获取),因此其value
为 null。这就是你设置的test
的值。结果应该在某个时候到达寻呼机LiveData
,但是您已经丢弃了它--您没有用新值更新test
。有很多方法可以解决这个问题(我觉得协程是最简单的),但是既然你已经在使用
LiveData
,你可以尝试使用switchMap
,它允许你创建一个LiveData
,它可以输出你在其他LiveData
对象之间切换的结果。这是通过使用
LiveData
作为您的 * 控件 * 来实现的-当该控件的值更改时,switchMap
调用一个返回LiveData
source 的函数。因此,基本上,您需要一个控件来保存 * 当前过滤类型 *,并且该函数需要 * 与repo对话以获取寻呼机LiveData
。因为这不是您的
ViewModel
代码当前的工作方式,所以您可能需要稍微重写一下。首先让我们得到一个过滤数据对象:因此,我们定义了所有可能的过滤器类型,以及每个类型所包含的数据。(对于你正在做的事情,你不需要这个,你可以使用一个带有可空
status
和gender
的数据类,并根据提供的数据来处理它,但这是一个更通用的方法,适用于任何情况)然后你会有一个私有的
LiveData
来保存当前的过滤器类型,还有一个公共函数来设置它:然后你把
test
连接起来,作为对_filter
中的变化做出React的东西:这基本上应该做到了。您可以通过调用例如
setFilter(DataFilter.Status(whatever))
和switchMap
通过调用适当的repo函数对更改做出React,并使用返回的LiveData
作为test
的新值源。由于test
本身从未被重新分配,因此无论观察它的是什么,它都会看到所有分页的值作为过滤更改而被输入。