![](/img/trans.png)
[英]RecyclerView doesn't display all items in list after update android studio
[英]Android RecyclerView items list doesn't change appropriately after chang settings and navigate again to fragment
我有一個活動 android 應用程序,一個具有任務管理的服務應用程序。 對於導航,我使用帶有底部導航的導航組件。 我還使用數據綁定和 Dagger2 DI,如果它對問題調查很重要的話。
用戶成功登錄后,會出現帶有隊列列表(水平回收視圖)的主屏幕。
主頁片段:-
每個隊列(recyclerview 項目)都有適當的任務列表可供用戶執行。
您可以 select 任何活動的隊列項目(其中至少包含一個任務)。 所選隊列的任務顯示在 recyclerview 下方,就像另一個垂直 recyclerview。 還有一個摘要隊列項(圖片上最左邊的項)計算並顯示所有可用隊列中的所有任務。
此摘要隊列項目的外觀取決於配置文件屏幕上的開關,表示為配置文件片段
簡介片段:-
設想:
問題:我希望看到摘要隊列項目不在隊列列表中,而且大多數情況下它在隊列列表中,但有時當我重復這個場景時它不在。 如果不是我 go 到配置文件片段或任何其他屏幕,然后 go 再次回到主屏幕,然后摘要隊列項目不在預期的列表中。
可能存在一些架構錯誤,這就是為什么我尋求真正的幫助並解釋問題發生的原因,因為我不僅希望解決它,而且還希望了解這種奇怪的行為。 我將在下面附上所有相關代碼! 提前謝謝了!
HomeFragment.kt
class HomeFragment : BaseFragment<HomeFragmentBinding>(), MenuItem.OnActionExpandListener {
@Inject lateinit var factory: HomeViewModelFactory
@Inject lateinit var viewModel: HomeViewModel
private lateinit var ticketsListAdapter: TicketsListAdapter
private lateinit var queuesListAdapter: QueuesListAdapter
private var searchView: SearchView? = null
private var pageLimit: Long = 10
private var offset: Long = 0L
private var selectedQueueId: Long = 0L
private var selectedQueueIndex: Int = 0
private var prevTicketsThreshold: Int = 0 // new
private var ticketsThreshold: Int = 0
private var lockId: Int = 1
private var allQueueIds: List<Long> = listOf()
private var isGeneralShoudlBeShown: Boolean = false
private var favoriteMode: Boolean = false
private lateinit var prefs: Prefs
private var selectedQueue: Queue? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
ComponentsHolder.getComponent().inject(this)
super.onViewCreated(view, savedInstanceState)
prefs = Prefs(requireContext())
(activity as MainActivity).showBottomNavigation()
(activity as MainActivity).getUnreadNotificationsCount()
val toolbar = view.findViewById(R.id.tickets_search_toolbar) as Toolbar
(activity as MainActivity).setSupportActionBar(toolbar)
toolbar.title = "Главная"
setHasOptionsMenu(true)
viewModel = ViewModelProvider(this, factory)[HomeViewModel::class.java]
binding.model = viewModel
binding.lifecycleOwner = this
with(viewModel) {
(activity as MainActivity).getPushToken { t ->
registerPushToken(t)
getUserSettings()
getUnreadNotificationsCount()
}
notificationscount.observe(viewLifecycleOwner) {
it?.let {
if (it.unreadCount > 0) {
(activity as MainActivity).setUnreadNotificationsCount(it.unreadCount)
.also { (activity as MainActivity).getUnreadNotificationsCount() }
}
}
}
checkUserSettings.observe(viewLifecycleOwner) {
isGeneralShoudlBeShown = it.isGeneralChecked
favoriteMode = it.isFavoritesChecked!!
getQueues(isGeneralShoudlBeShown, favoriteMode, selectedQueueIndex)
}
queueIds.observe(viewLifecycleOwner) {
it?.let {
allQueueIds = it
}
}
queues.observe(viewLifecycleOwner) {
it?.let {
when (it.responseCode) {
200 -> {
queuesListAdapter.submitList(it.queues)
queuesListAdapter.notifyDataSetChanged()
retrieveSelectedQueue(it.queues)
getTickets(
if (selectedQueueId == 0L) 0 else selectedQueueId,
if (selectedQueueId == 0L) allQueueIds else emptyList(),
lockId,
pageLimit,
offset
)
}
}
}
}
tickets.observe(viewLifecycleOwner) {
it?.let {
binding.refreshDate.text = getLastRefreshDateTime()
Log.i("hmfrgmnt", it.toString())
when (it.responseCode) {
401 -> {
binding.bottomProgress.visibility = View.GONE
if (mayNavigate()) {
findNavController().navigate(
HomeFragmentDirections
.actionHomeFragmentToSplashFragment()
)
}
}
200 -> {
binding.bottomProgress.visibility = View.GONE
ticketsListAdapter.submitList(null)
ticketsListAdapter.notifyDataSetChanged()
}
else -> (activity as MainActivity).showErrorDialog(
it.responseMessage!!,
null
)
}
}
}
navigateToTicketDetails.observe(viewLifecycleOwner) { ticketId ->
ticketId?.let {
if (mayNavigate()) {
findNavController().navigate(
HomeFragmentDirections
.actionHomeFragmentToTicketDetailsFragment(ticketId)
)
}
viewModel.onTicketDetailsNavigated()
}
}
}
with(binding) {
tabs.selectTab(tabs.getTabAt((lockId - 1)), true)
(queueList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(selectedQueueIndex, queueList.top)
ticketsListAdapter = TicketsListAdapter(TicketsListListener { ticketId ->
viewModel.onTicketDetailsClicked(ticketId)
})
queuesListAdapter = QueuesListAdapter(
QueuesListListener { queue ->
setActiveQueueData(queue)
tabs.selectTab(tabs.getTabAt((lockId - 1)), true)
viewModel.onQueueClicked(if (queue.queueId == 0L) 0 else selectedQueueId, if (queue.queueId == 0L) allQueueIds else emptyList(), lockId, pageLimit, offset)
// ticketsListAdapter.notifyDataSetChanged()
}
)
ticketsList.adapter = ticketsListAdapter
queueList.adapter = queuesListAdapter
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
when (tab?.position) {
1 -> {
offset = 0
lockId = 2
viewModel.onQueueClicked(if (selectedQueueId == 0L) 0 else selectedQueueId, if (selectedQueueId == 0L) allQueueIds else emptyList(), lockId, pageLimit, offset)
}
else -> {
offset = 0
lockId = 1
viewModel.onQueueClicked(if (selectedQueueId == 0L) 0 else selectedQueueId, if (selectedQueueId == 0L) allQueueIds else emptyList(), lockId, pageLimit, offset)
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}
})
nestedScroll.setOnScrollChangeListener { v, _, scrollY, _, _ ->
if ((scrollY > (v as NestedScrollView).getChildAt(0).measuredHeight - v.measuredHeight - homeMainLayout.paddingBottom) && viewModel.status.value != ApiStatus.LOADING) {
if (ticketsThreshold > prevTicketsThreshold) {
if (ticketsThreshold < pageLimit || ticketsThreshold == 0) {
moreButton.visibility = View.GONE
endOfListView.visibility = View.VISIBLE
} else {
moreButton.visibility = View.VISIBLE
endOfListView.visibility = View.GONE
}
} else if (ticketsThreshold == prevTicketsThreshold) {
moreButton.visibility = View.GONE
endOfListView.visibility = View.VISIBLE
} else {
moreButton.visibility = View.VISIBLE
endOfListView.visibility = View.GONE
}
}
}
refreshButton.setOnClickListener {
offset = 0
viewModel.refresh(isGeneralShoudlBeShown, favoriteMode, selectedQueueIndex, selectedQueueId, allQueueIds, lockId, pageLimit, offset)
(queueList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(selectedQueueIndex, queueList.top)
tabs.selectTab(tabs.getTabAt((lockId - 1)), true)
queuesListAdapter.notifyDataSetChanged()
}
moreButton.setOnClickListener {
prevTicketsThreshold = ticketsThreshold
offset += pageLimit
viewModel.getTickets(
if (selectedQueueId == 0L) 0 else selectedQueueId,
if (selectedQueueId == 0L) allQueueIds else emptyList(),
lockId,
pageLimit,
offset
)
}
}
}
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = HomeFragmentBinding.inflate(inflater, container, false)
private fun setActiveQueueData(queue: Queue) {
offset = 0
selectedQueue = queue
prefs.queueObject = queue
binding.selectedQueueTitle.text = queue.title
selectedQueueIndex = queuesListAdapter.currentList.getQueuePosition(selectedQueue as Queue) ?: 0
queuesListAdapter.currentList.forEach { i -> i.isSelected = false }
queuesListAdapter.notifyDataSetChanged()
queuesListAdapter.selectItem(selectedQueueIndex)
(binding.queueList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(selectedQueueIndex, binding.queueList.top)
}
private fun saveSelectedQueueBeforeNavigating(selectedQueue: Queue) {
prefs.queueObject = selectedQueue
}
override fun onDestroyView() {
super.onDestroyView()
Log.i("profileSaveQueue", "i will save queue: $selectedQueue")
saveSelectedQueueBeforeNavigating(selectedQueue!!)
}
}
HomeViewModel.kt
class HomeViewModel @Inject constructor(
private val userRepository: UserRepository,
private val ticketsRepository: TicketsRepository,
private val queuesRepository: QueuesRepository,
private val notificationsRepository: NotificationsRepository,
private val pushRepository: PushRepository
) : BaseViewModel() {
private var ticketsList: MutableList<Ticket> = mutableListOf()
private var summaryTicketsCount: Int? = 0
private val _status = MutableLiveData<ApiStatus>()
val status: LiveData<ApiStatus>
get() = _status
private val _notificationsCount = MutableLiveData<NoticeCountResponse?>()
val notificationscount: LiveData<NoticeCountResponse?>
get() = _notificationsCount
private val _tickets = MutableLiveData<TicketsResponse?>()
val tickets: LiveData<TicketsResponse?>
get() = _tickets
private val _navigateToTicketDetails = MutableLiveData<Long?>()
val navigateToTicketDetails
get() = _navigateToTicketDetails
private val _queues = MutableLiveData<QueuesResponse?>()
val queues: LiveData<QueuesResponse?>
get() = _queues
private val _queueIds = MutableLiveData<List<Long>?>()
val queueIds: LiveData<List<Long>?>
get() = _queueIds
private val _checkUserSettings = MutableLiveData<User>()
val checkUserSettings: LiveData<User>
get() = _checkUserSettings
fun refresh(showGeneral: Boolean, favoriteOnly: Boolean, selectedQueueIndex: Int, queueId: Long, queueIds: List<Long>?, lockId: Int?, limit: Long?, offset: Long?) {
ticketsList = mutableListOf()
getQueues(showGeneral, favoriteOnly, selectedQueueIndex)
}
fun getUserSettings() {
viewModelScope.launch {
_checkUserSettings.value = retrieveUserSettings()
}
}
private suspend fun retrieveUserSettings(): User? {
return withContext(Dispatchers.IO) {
userRepository.getUserInfo()
}
}
fun getUnreadNotificationsCount() {
_status.value = ApiStatus.LOADING
viewModelScope.launch {
kotlin.runCatching { notificationsRepository.getUnreadNotificationsCount("Bearer ${getToken()}") }
.onSuccess {
_notificationsCount.value = it
_status.value = ApiStatus.DONE
}
.onFailure {
_status.value = ApiStatus.DONE
}
}
}
fun registerPushToken(token: String) {
viewModelScope.launch {
pushRepository.registerToken("Bearer ${getToken()}", TokenRegisterBody(token, 1))
}
}
fun getQueues(showGeneral: Boolean, favoriteOnly: Boolean, selectedQueueIndex: Int) {
_status.value = ApiStatus.LOADING
viewModelScope.launch {
kotlin.runCatching { queuesRepository.getQueuesListWithTicketsCount("Bearer ${getToken()}", favoriteOnly) }
.onSuccess { value ->
summaryTicketsCount = value.queues?.mapNotNull { q -> q.ticketsCount }?.sum()
val queuesList: List<Queue> = sortQueues(value.queues, selectedQueueIndex, showGeneral)
_queueIds.value = value.queues?.map { item -> item.queueId }
_queues.value = QueuesResponse(queuesList, value.responseCode, value.responseMessage)
_status.value = ApiStatus.DONE
}
.onFailure {
if (it is HttpException) {
_queues.value = QueuesResponse(null, it.code(), getResponseMessage(it))
_status.value = ApiStatus.DONE
}
else {
_queues.value = QueuesResponse(null, -1, "Что-то пошло не так")
_status.value = ApiStatus.DONE
}
}
}
}
fun getTickets(queueId: Long?, queueIds: List<Long>?, lockId: Int?, limit: Long?, offset: Long?) {
_status.value = ApiStatus.LOADING
val body = TicketsListBody(queueId = queueId, queueIds = queueIds, lockId = lockId, limit = limit, offset = offset)
viewModelScope.launch {
kotlin.runCatching { ticketsRepository.getTickets("Bearer ${getToken()}", body) }
.onSuccess {
it.tickets?.forEach { ticket -> if (ticket !in ticketsList) { ticketsList.add(ticket) } }
_tickets.value = TicketsResponse(ticketsList, it.responseCode, it.responseMessage)
_status.value = ApiStatus.DONE
}
.onFailure {
if (it is HttpException) {
_tickets.value = TicketsResponse(null, it.code(), getResponseMessage(it))
_status.value = ApiStatus.DONE
}
else {
_tickets.value = TicketsResponse(null, -1, "Что-то пошло не так")
_status.value = ApiStatus.DONE
}
}
}
}
private fun sortQueues(queues: List<Queue>?, selectedQueueIndex: Int, showGeneral: Boolean): List<Queue> {
val favoriteQueuesList: List<Queue>? = queues?.toMutableList()
?.filter { a -> a.isInFavoritesList }
?.sortedByDescending { b -> b.ticketsCount }
val restQueuesList: List<Queue>? = queues?.toMutableList()
?.filter { a -> !a.isInFavoritesList }
?.sortedByDescending { b -> b.ticketsCount }
val queuesList: List<Queue> = mutableListOf<Queue>()
.also { items ->
if (showGeneral) {
items.add(0, Queue(0, null, summaryTicketsCount, true,false))
}
favoriteQueuesList?.forEach { a -> items.add(a) }
restQueuesList?.forEach { a -> items.add(a) }
items[selectedQueueIndex].isSelected = true
}
return queuesList
}
fun onTicketDetailsClicked(id: Long) { _navigateToTicketDetails.value = id }
fun onTicketDetailsNavigated() { _navigateToTicketDetails.value = null }
fun onQueueClicked(id: Long, ids: List<Long>?, lockId: Int?, limit: Long?, offset: Long) {
ticketsList = mutableListOf()
getTickets(id, ids, lockId, limit, offset)
}
private suspend fun getToken(): String? {
return withContext(Dispatchers.IO) {
userRepository.getUserInfo()?.sessionValue
}
}
fun logout() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
userRepository.clean()
}
}
}
override fun onCleared() {
super.onCleared()
ticketsList = mutableListOf()
}
}
隊列列表適配器.kt
class QueuesListAdapter (val clickListener : QueuesListListener):
ListAdapter<Queue, QueuesListAdapter.ViewHolder>(DIFF_CALLBACK) {
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Queue>() {
override fun areItemsTheSame(oldItem: Queue, newItem: Queue): Boolean {
return oldItem.queueId == newItem.queueId
}
override fun areContentsTheSame(oldItem: Queue, newItem: Queue): Boolean {
return oldItem == newItem
}
}
private var statesMap = HashMap<Int,Boolean>()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
setItemView(item, holder.binding)
holder.bind(item, clickListener)
item.isSelected = statesMap[position] != null
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
fun selectItem(position: Int) {
val item = getItem(position)
item.isSelected = true
statesMap.clear()
statesMap[position] = item.isSelected
notifyItemChanged(position)
}
private fun setItemView(item: Queue, binding: ItemQueueBinding) {
when (item.isSelected) {
true -> {
item.isSelected = false
binding.queueContent.setBackgroundResource(R.drawable.item_selected_queue_background)
binding.queueContent.alpha = 1F
}
false -> {
binding.queueContent.setBackgroundResource(R.drawable.item_queue_background)
if (item.ticketsCount == 0) {
binding.queueContent.isEnabled = false
binding.queueContent.isFavoriteIcon.isEnabled = false
binding.queueContent.alpha = 0.3F
} else {
binding.queueContent.isEnabled = true
binding.queueContent.isFavoriteIcon.isEnabled = true
binding.queueContent.alpha = 1F
}
}
}
}
class ViewHolder private constructor(val binding: ItemQueueBinding): RecyclerView.ViewHolder(
binding.root
) {
fun bind(item: Queue, clickListener: QueuesListListener) {
binding.queues = item
binding.clickListener = clickListener
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemQueueBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class QueuesListListener(val clickListener: (queue: Queue) -> Unit) {
fun onClick(queue: Queue) {
clickListener(queue)
}
}
ProfileFragment.kt
class ProfileFragment : BaseFragment<ProfileFragmentBinding>() {
@Inject lateinit var factory: ProfileViewModelFactory
@Inject lateinit var viewModel: ProfileViewModel
private lateinit var profileQueuesListAdapter: ProfileQueuesListAdapter
private var initialQueuesList = mutableListOf<Queue>()
private var favorites = mutableMapOf<Long,Boolean>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
ComponentsHolder.getComponent().inject(this)
super.onViewCreated(view, savedInstanceState)
(activity as MainActivity).showBottomNavigation()
(activity as MainActivity).getUnreadNotificationsCount()
viewModel = ViewModelProvider(this, factory)[ProfileViewModel::class.java]
binding.model = viewModel
binding.lifecycleOwner = this
with(viewModel) {
getUserSettings()
checkUserSettings.observe(viewLifecycleOwner) {
it?.let {
favoritesSwitchItem.isChecked = it.isFavoritesChecked!!
generalQueueSwitchItem.isChecked = it.isGeneralChecked
}
}
loggedOut.observe(viewLifecycleOwner) {
it?.let {
if (mayNavigate()) {
findNavController().navigate(
ProfileFragmentDirections
.actionProfileFragmentToLoginFragment()
)
}
}
}
}
with(binding) {
profileAppBar.toolbar.title = "Профиль"
logoutButton.setOnClickListener { viewModel.logout() }
appVersionDescription.text = requireContext().packageManager.getPackageInfo(requireContext().packageName, 0).versionName
generalQueueSwitchItem.setOnCheckedChangeListener { _, _ ->
if (generalQueueSwitchItem.isChecked) {
viewModel.updateGeneralQueueState(true)
} else {
viewModel.updateGeneralQueueState(false)
}
}
favoritesSwitchItem.setOnCheckedChangeListener { _, _ ->
if (favoritesSwitchItem.isChecked) {
viewModel.updateFavoritesState(true)
} else {
viewModel.updateFavoritesState(false)
}
}
}
}
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = ProfileFragmentBinding.inflate(inflater, container, false)
}
ProfileViewModel.kt
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository,
private val queuesRepository: QueuesRepository
) : BaseViewModel() {
private var summaryTicketsCount: Int? = 0
private var addToFavoritesList = mutableListOf<Long>()
private var removeFromFavoritesList = mutableListOf<Long>()
private val _status = MutableLiveData<ApiStatus>()
val status: LiveData<ApiStatus>
get() = _status
private val _queues = MutableLiveData<QueuesResponse?>()
val queues: LiveData<QueuesResponse?>
get() = _queues
private val _loggedOut = MutableLiveData<Boolean>()
val loggedOut : LiveData<Boolean>
get() = _loggedOut
private val _checkUserSettings = MutableLiveData<User>()
val checkUserSettings: LiveData<User>
get() = _checkUserSettings
init { }
fun logout() {
coroutineScope.launch {
clean().also { _loggedOut.value = true }
}
}
fun getUserSettings() {
coroutineScope.launch {
_checkUserSettings.postValue(retrieveUserSettings())
}
}
private suspend fun retrieveUserSettings(): User? {
return withContext(Dispatchers.IO) {
userRepository.getUserInfo()
}
}
fun updateGeneralQueueState(isShouldBeShown: Boolean) {
_status.value = ApiStatus.LOADING
coroutineScope.launch {
updateGeneralQueue(isShouldBeShown)
_status.value = ApiStatus.DONE
}
}
fun updateFavoritesState(isFavoritesActive: Boolean) {
_status.value = ApiStatus.LOADING
coroutineScope.launch {
updateFavorites(isFavoritesActive)
_status.value = ApiStatus.DONE
}
}
private suspend fun updateGeneralQueue(isShouldBeShown: Boolean) {
withContext(Dispatchers.IO) {
userRepository.updateGeneralQueueState(isShouldBeShown)
}
}
private suspend fun updateFavorites(isFavoritesActive: Boolean) {
withContext(Dispatchers.IO) {
userRepository.updateFavoritesState(isFavoritesActive)
}
}
private suspend fun getToken(): String? {
return withContext(Dispatchers.IO) {
userRepository.getUserInfo()?.sessionValue
}
}
private suspend fun clean() {
withContext(Dispatchers.IO) {
userRepository.clean()
}
}
}
我不知道到底發生了什么,因為這是很多代碼並且很難逐行跟蹤; 在 SO 上閱讀代碼時很容易遺漏一些東西,但我確實看到了一些可以改進架構的東西。
我要開始的地方是查看您的體系結構中具有“代碼味道”的部分(注意:我將編寫的大部分代碼都是偽代碼,而且我沒有使用數據綁定的經驗(僅ViewBinding),因為我不喜歡它的功能,而且我一直選擇不使用它,所以如果數據綁定導致問題,我不確定。 )
在利用協同程序的強大功能時,您希望受益於使用掛起函數的能力,以及 LiveData(或 Flow)的反應性質,以便在您的 UI 中觀察和做出反應。 我不會 go 對每個主題進行過多的詳細介紹,但是當我看到潛在的可測試性問題時,我會提到它們,因為您需要對您的業務邏輯進行單元測試,為此,您應該考慮一些事情。
一般來說,你會想要遵循 Jetpack 架構的想法(除非你為 Square 工作,在這種情況下一切都必須不同,因為谷歌是錯誤的); 考慮到這一點,我將在適用的情況下遵循Google 推薦的做法,因為如果您不喜歡它,您可以找到自己的替代方案;)
我在碎片中看到了很多state。 許多布爾值、整數、列表等。這通常是一個危險信號。 你有一個 ViewModel,那是你的 state 應該來自的地方,片段很少有理由在本地“存儲”這個 state。
我覺得您使用了很多LiveData,這很好,但我相信您可以通過將其中的大部分替換為組合流來進一步受益。 你的每個內部狀態都是一個流,如果你想拆分你的反應代碼的一部分,你可以暴露給片段一個(組合)或一對。 通過在您的 VM 中使用combine(flow1, flow2, etc...)
function,您可以生成一個更具凝聚力的 state,甚至將其公開為StateFlow
以提高效率,因為您隨后會觀察來自你的片段使用類似的東西:
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.yourFlow.collect {...} //or collectLatest, depending on your usecase
}
這是可選的,但它比有這么多 liveDatas 浮動更好。
我看到您有兩個或三個 ListView 適配器(很好),但它們實際上不需要是 lateinit。 你並沒有真正添加太多,在初始化時創建它們:
private val adapter1 = SomeAdapter()
private val adapter2 = AnotherAdater()
由於它們是 ListAdapter,一旦您通過 (livedata/flow) 接收數據,您應該做的就是adapter1.submitList(...)
,因為它們永遠不可能是 null。 通過使用 lateinit (在你知道你無論如何都會需要的東西中)你並沒有真正獲得任何東西,並且正在引入復雜性。 你可以做比lateinit
更好的優化。
最后,您的片段應盡可能虛擬。 您在顯示它時“加載它”,遵守它瘋狂的生命周期,然后將所有東西連接起來,以便它可以觀察實時數據/流並使用傳入的 state 更新其 UI,這就是它應該做的。 當然還有導航,但主要是因為它是您應該在 android 框架中執行的必需管道的一部分。
如果你添加更多的邏輯/東西/狀態,你就會把自己置於一個測試角落和一個更復雜的場景來管理,因為片段被銷毀、分離、重新添加等。
使用列表適配器做得很好,但您的適配器有一些問題。
notifyDataSetChanged
,它會破壞目的。 提交列表應該可以解決問題,這就是您擁有 DiffUtil 的原因。 如果這不起作用,那么您可能需要注意 ListAdapter 的其他問題,但是一旦您解決了這些問題,您應該可以使用 go。以您的代碼片段為例:
fun selectItem(position: Int) {
val item = getItem(position)
item.isSelected = true
statesMap.clear()
statesMap[position] = item.isSelected
notifyItemChanged(position)
}
為什么會有一個static hashMap來表示選擇呢?
Adapter 在幕后已經有很多工作要做,它不應該承擔這個責任。
選擇某些內容后,您將對其進行一些處理(將一些 boolean 設置為 true,例如yourItem.isSelected = true
),然后生成一個新列表,該列表將提交給適配器,diffutil 將選擇更改。
(這只是改變您的列表的操作示例,它可能是其他操作,但原則是,不要將 state 保留在它不屬於的地方,而是對通過預期渠道收到的更改做出反應)。
這看起來不錯,但我覺得你沒有正確委派你的職責。 你的適配器不應該有很多 if 語句。 如果選擇了一個項目,ViewHolder 應該接收此信息並采取相應行動,而不是 Adapter。 因此,我會將 boolean 與 ViewHolder 設置其正確外觀所需的所有信息一起傳遞給您的fun bind
。 無論如何,這都是只讀信息。
小心在整個地方硬編碼 Dispatchers.IO,這使得無法正確測試,因為您無法輕易覆蓋調度程序。 相反,注入它,因為您已經在使用 Dagger。 這樣您的測試將能夠覆蓋它們。
在 viewModel 中總是這樣做
viewModelScope.launch(injected_dispatcher) {
//call your suspend functions and such
val result = someRepo.getSomething()
someFlow.emit(result)
}
(只是一個例子)。
當您測試您的 VM 時,您將提供一個測試調度程序。
總的來說,在架構上做得很好,這比完成所有工作的大型活動要好;)
我覺得您可以稍微簡化您的代碼,這反過來將極大地幫助您找到未按預期運行的部分。
記住。 Activity/Fragment 觀察 ViewModel,並處理 Android 框架事物(Google 稱它們為“策略委托”),如導航、意圖等。它們對接收到的數據做出反應並將其傳遞(例如,傳遞給適配器)。 viewModel 位於您的真實來源(存儲庫、數據層)和您的業務邏輯(拆分為用例/交互器或您所說的任何東西)之間。 ViewModel 可以為脆弱的 Fragments/Activities 提供更穩定、壽命更長的組件,將數據與 UI 粘合在一起。
回購協議/等。 都是suspend函數,從源頭返回你需要的數據,需要的時候再更新。 (例如,與 Room DB 或 API 或兩者交談。)它們僅返回數據供 VM 和更高級別使用。
用例/交互器只是“重用”視圖模型和存儲庫等(或某些特定邏輯)之間的通信的抽象。 他們可以在他們認為合適的時候對您的數據應用轉換,從而將虛擬機從這種責任中解放出來。
例如,如果您有一個 GetSomethingUseCase,它可能會在幕后與存儲庫對話,等待(暫停),然后將 API/DB 響應轉換為 UI 所需的內容,所有這些都在沒有 VM(或 UI)的情況下完成知道發生了什么。
最后,使您的適配器盡可能小。 請記住,他們已經承擔了很多責任。
我看到這個工作的方式是。
片段 1 開始,VM 正在初始化。 片段在啟動時通過一些實時數據/流觀察其 state。 片段改變其視圖以匹配收到的 state。
用戶轉到片段 2 並更改某些內容,此“內容”更新數據庫或內存中的數據結構。
用戶回到Fragment 1,又是init還是restored,不過這次又觀察了liveData/Flow,數據又回來了(現在被fragment2修改了)。 Fragment 更新其 UI 而無需考慮太多,因為這不是它的責任。
我希望這個冗長的答案能為您指明正確的方向。
除此之外,我建議您將問題分解為一個更小的問題,以嘗試找出沒有做應做的事情。 隨機位置(適配器、片段等)中的“狀態”越少,出現奇怪問題的可能性就越小。
不要與框架作斗爭;)
最后,如果你做到了這一點,如果你向適配器提交了一個列表但它沒有更新, 請查看這個 SO 問題/答案,因為 ListAdapter 有一個“根據谷歌,這不是錯誤,但文檔沒有”不夠清楚”的情況。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.