You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Flow<BleScanResult>转换为Flow<List<BleScanResult>>并实现去重?——Android BLE扫描场景技术问询

Hey there! Let's break down your problem and fix it step by step.

First, you're right about the core issue: your bluetoothScan() emits individual ScanResult objects one at a time, but RecyclerView.Adapter.submitList() needs a full list of items to display. Plus, you want to avoid duplicate entries since BLE scans often report the same device multiple times. Let's address both the Flow transformation and any conceptual gaps you might have.

1. Fixing the Flow-to-List Transformation with Deduplication

The key here is to use the scan operator on your Flow. scan lets you maintain a running state (in this case, a list of scanned devices) and update it each time a new item is emitted. We'll use the device's MAC address (unique for every Bluetooth device) to check for duplicates.

Update your ViewModel code like this:

val scanResults: LiveData<List<BleScanResult>> = scanEnabled.flatMapLatest { doScan -> 
    if (doScan) { 
        repository.bluetoothScan()
            .map { BleScanResult(it.device.name, it.device.address) }
            // Accumulate results into a list, deduplicating by device address
            .scan(emptyList<BleScanResult>()) { currentList, newItem ->
                // Check if this device is already in the list
                val existingItemIndex = currentList.indexOfFirst { it.address == newItem.address }
                
                if (existingItemIndex != -1) {
                    // If exists, replace it (in case the device name updated)
                    currentList.toMutableList().apply {
                        set(existingItemIndex, newItem)
                    }
                } else {
                    // If not, add the new device to the list
                    currentList + newItem
                }
            }
    } else { 
        // When scanning stops, emit an empty list to clear the UI
        flowOf(emptyList())
    } 
}.asLiveData()

2. Update Your Activity Observation

Now that your scanResults LiveData holds a List<BleScanResult>, you can safely pass it to submitList():

val adapter = ScanResultListAdapter() 
binding.rcBleScanResults.adapter = adapter 

viewModel.scanResults.observe(this) { results ->
    adapter.submitList(results)
}

3. Clarifying the Conceptual Gap

You weren't wrong in your initial approach—you just missed the step of accumulating individual emissions into a persistent list. Flow is designed to emit streams of data, which can be single items or collections. For RecyclerView, we need a full snapshot of the current dataset at all times, hence the need to build and maintain that list as new devices are scanned.

A few extra notes to avoid common pitfalls:

  • Why scan instead of other operators? Unlike reduce, scan emits every intermediate state (so you get the updated list every time a new device is added/updated), which is perfect for keeping the UI in sync.
  • Deduplication logic: Using the device's address is reliable because MAC addresses are unique to each Bluetooth device. If you only check the name, you might get duplicates (since multiple devices can have the same name).
  • Stopping scans cleanly: When doScan switches to false, emitting flowOf(emptyList()) ensures the UI clears immediately. Also, make sure your repository's bluetoothScan() properly stops scanning when the Flow is cancelled (which flatMapLatest handles automatically by cancelling the previous Flow when scanEnabled changes).

内容的提问来源于stack exchange,提问作者Blubberlutsch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:07:38