Итак, у меня есть 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()
}
}
Это для отображения индикатора выполнения во время загрузки, но я не думаю, что это вызывает какие-либо проблемы.
Я подозреваю, что, возможно, lifecycleScope все еще активен и не запускается повторно. Возможно, вы ожидаете, что он отменит и повторно запустит код внутри.
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) должен справиться с этим
я заметил, что когда мы возвращаемся к FragmentA, RecyclerView пуст, но теперь, если я поворачиваю экран, все элементы отображаются
Это связано с изменениями жизненного цикла и повторным запуском ваших потоков.
Давайте продолжим обсуждение в чате.
Решено (наконец-то)!
После нескольких часов поиска, отладки и прочего все заработало!
Вот что сработало:
В 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
}
Не знаю почему, но это работает, но не тот код, о котором идет речь.
Что вы делаете с UiState?