Почему элементы в RecyclerView не отображаются при нажатии кнопки «Назад»?

Итак, у меня есть FragmentA. Он содержит RecyclerView. Сначала он показывает список элементов, как и ожидалось. Теперь, когда пользователь нажимает на один из элементов, мы переходим к FragmentB (FragmentA все еще находится в стеке). Когда пользователь нажимает кнопку «Назад», стек выскакивает, и теперь мы находимся на FragmentA, но теперь RecyclerView пуст.

Для всего этого я использую ViewModel, MutableStateFlow и StateFlow. Вот код:

Модель просмотра:

private val _uiData = MutableStateFlow(UIData())
val uiData = _uiData.asStateFlow()
private val _uiState = Channel<UIState>()
val uiState = _uiState.receiveAsFlow()
fun onUIEvent(uiEvent: UIEvent) = when (uiEvent) {
  is UIEvent.LoadBondedDevices -> loadBondedDevices(uiEvent.bluetoothDevices)
}
private fun loadBondedDevices(bluetoothDevices: Set<BluetoothDevice?>) {
    _uiState.trySend(UIState.Loading)
    viewModelScope.launch {
        val bondedDevices = bluetoothDevices.filterNotNull()
            .map { bluetoothDevice: BluetoothDevice ->
                BondedDevice(
                    bluetoothDevice.name,
                    bluetoothDevice.address
                )
            }
        _uiData.update { uiData: UIData -> uiData.copy(bondedDevices = bondedDevices) }
        _uiState.trySend(UIState.Idle)
    }
}

ФрагментА:

@AndroidEntryPoint
class FragmentA : Fragment(R.layout.fragment_a) {
    private val viewBinding by lazy {
        FragmentABinding.bind(requireView())
    }
    private val viewModel by viewModels<FragmentAViewModel>()
    private val bluetoothAdapter by lazy {
        (requireContext()
            .applicationContext
            .getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
    }
    private val bondedDevicesAdapter by lazy {
        BondedDevicesRecyclerViewAdapter { bondedDevice: BondedDevice ->
            // An item is clicked
            // Navigate to FragmentB
            findNavController().navigate(
                FragmentADirections.actionFragmentAToFragmentB(
                    bondedDevice
                )
            )
        }
    }
    private val bluetoothStateBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent == null) return
            val bluetoothState = intent.getIntExtra(
                BluetoothAdapter.EXTRA_STATE,
                BluetoothAdapter.STATE_TURNING_OFF,
            )
            if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
                // Bluetooth is turning off
                try {
                // Navigate to FragmentC
                } catch (ignored: Exception) {}
            }
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Check if Bluetooth is supported
        if (bluetoothAdapter == null) {
            // Bluetooth is not supported
            // Navigate to FragmentD
            return
        }
        viewBinding.recyclerView.adapter = bondedDevicesAdapter
        viewBinding.refreshEfab.setOnClickListener { loadBondedDevices() }
        // Listen for UIData changes and show them on screen
        lifecycleScope.launch {
            viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                viewModel.uiData.collect { uiData: UIData ->
                    if (uiData.bondedDevices.isEmpty()) {
                        viewBinding.emptyMtv.visibility = View.VISIBLE
                        viewBinding.recyclerView.visibility = View.GONE
                    } else {
                        viewBinding.emptyMtv.visibility = View.GONE
                        viewBinding.recyclerView.visibility = View.VISIBLE
                    }
                    bondedDevicesAdapter.submitList(uiData.bondedDevices)
                }
            }
        }
        // Listen for UIState changes and show them on screen
        lifecycleScope.launch {
            viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                viewModel.uiState.collect { uiState: UIState ->
                    when (uiState) {
                        UIState.Idle -> viewBinding.linearProgressIndicator.hide()
                        UIState.Loading -> viewBinding.linearProgressIndicator.show()
                    }
                }
            }
        }
        // Register receiver to monitor Bluetooth state
        requireActivity().registerReceiver(
            bluetoothStateBroadcastReceiver,
            IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        )
        loadBondedDevices()
    }

    override fun onDestroy() {
        super.onDestroy()
        // Unregister the registered receiver
        try {
            requireActivity().unregisterReceiver(bluetoothStateBroadcastReceiver)
        } catch (ignored: Exception) {
        }
    }

    private fun loadBondedDevices() {
        // Check Android version and Bluetooth permissions
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (ActivityCompat.checkSelfPermission(
                    requireContext(),
                    Manifest.permission.BLUETOOTH_CONNECT
                ) != PackageManager.PERMISSION_GRANTED ||
                ActivityCompat.checkSelfPermission(
                    requireContext(),
                    Manifest.permission.BLUETOOTH_SCAN
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                // We don't have all Bluetooth permissions
                // Navigate to FragmentE
                return
            }
        }
        // We have all Bluetooth permissions
        // Check if Bluetooth is enabled
        if (!bluetoothAdapter.isEnabled) {
            // Bluetooth is disabled
            // Navigate to FragmentC
            return
        }
        // All good
        // Load list of bonded devices
        viewModel.onUIEvent(UIEvent.LoadBondedDevices(bluetoothAdapter.bondedDevices))
    }
}

RecyclerViewAdapter — это ListAdapter.

class BondedDevicesRecyclerViewAdapter @Inject constructor(
    private val onClick: (BondedDevice) -> Unit,
) : ListAdapter<BondedDevice, BondedDevicesRecyclerViewAdapter.ViewHolder>(
    BondedDeviceDiffItemCallback()
) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            ItemBondedDeviceBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false,
            )
        )
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
        holder.bind(getItem(position))
    }

    inner class ViewHolder(
        private val viewBinding: ItemBondedDeviceBinding,
    ) : RecyclerView.ViewHolder(viewBinding.root) {
        init {
            viewBinding.root.setOnClickListener { onClick(getItem(absoluteAdapterPosition)) }
        }

        fun bind(bondedDevice: BondedDevice) {
            viewBinding.titleMtv.text = bondedDevice.name
            viewBinding.subtitleMtv.text = bondedDevice.address
        }
    }
}

class BondedDeviceDiffItemCallback : DiffUtil.ItemCallback<BondedDevice>() {
    override fun areItemsTheSame(oldItem: BondedDevice, newItem: BondedDevice): Boolean {
        return oldItem.address == newItem.address
    }

    override fun areContentsTheSame(oldItem: BondedDevice, newItem: BondedDevice): Boolean {
        return oldItem.hashCode() == newItem.hashCode()
    }
}

Что вы делаете с UiState?

gtxtreme 10.06.2024 19:13

Это для отображения индикатора выполнения во время загрузки, но я не думаю, что это вызывает какие-либо проблемы.

dev.tejasb 10.06.2024 19:32

Я подозреваю, что, возможно, lifecycleScope все еще активен и не запускается повторно. Возможно, вы ожидаете, что он отменит и повторно запустит код внутри.

gtxtreme 10.06.2024 19:41
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.Sta‌​te.RESUMED) должен справиться с этим
dev.tejasb 10.06.2024 19:52

я заметил, что когда мы возвращаемся к FragmentA, RecyclerView пуст, но теперь, если я поворачиваю экран, все элементы отображаются

dev.tejasb 10.06.2024 19:56

Это связано с изменениями жизненного цикла и повторным запуском ваших потоков.

gtxtreme 10.06.2024 20:24

Давайте продолжим обсуждение в чате.

dev.tejasb 11.06.2024 00:10
0
7
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Решено (наконец-то)!

После нескольких часов поиска, отладки и прочего все заработало!

Вот что сработало:

В FragmenA я использовал by lazy для привязки представления, что создавало проблему. Я изменил его на lateinit var и инициализировал onCreateView следующим образом:

ФрагментА:

@AndroidEntryPoint
class FragmentA : Fragment(R.layout.fragment_a) {
    private lateinit var viewBinding: AFragmentBinding
    // Unchanged code from question
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        viewBinding = AFragmentBinding.inflate(inflater)
        return viewBinding.root
    }
    // Unchanged code from question
}

Не знаю почему, но это работает, но не тот код, о котором идет речь.

Другие вопросы по теме