在Android应用程序的数据层中使用BroadcastReceiver

问题描述 投票:0回答:1

我是移动开发新手,我正在尝试使用 Kotlin 和 Jetpack Compose 构建我的第一个 Android 应用程序。实际的应用程序应该只扫描可见的接入点并将它们显示在列表中。即使应用程序非常简单,我也想遵循推荐的应用程序架构,因此我遵循官方文档中的以下指南。这意味着我有包含可组合项和

ViewModel
(我的状态持有者)的 UI 层,以及包含业务逻辑的数据层。

我遇到的问题是我需要注册一个广播侦听器,然后请求扫描(请参阅this),但为此我需要使用

context
,这在我的 ViewModel 和我的 Repository 类中都不可用。另外,我找不到将扫描结果传递给 ViewModel 的最佳方法。

这是我的简单 UI(MainActivity):

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MyTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    AccessPointsListScreen()
                }
            }
        }
    }

@Composable
fun AccessPointsListScreen(
    accessPointsViewModel: AccessPointsViewModel = viewModel())
) {
    // State to hold the scan results
    val accessPointsUiState by accessPointsViewModel.uiState.collectAsState()

    AccessPoints(
        accessPointsList = accessPointsUiState.accessPointsList
    )
}

@Composable
private fun AccessPoints(
    accessPointsList: List<AccessPoint>
) {
    // Omit implementation of UI
}

这是我的 ViewModel 类:

/**
 * UI state for the Access Points
 */
data class AccessPointsUiState(
    val accessPointsList: List<AccessPoint> = emptyList()
)

/**
 * ViewModel that handles the logic of the access points screen
 */
class AccessPointsViewModel(
    private val accessPointsRepository: AccessPointsRepository
): ViewModel() {
    private val _uiState = MutableStateFlow(AccessPointsUiState())
    val uiState: StateFlow<AccessPointsUiState> = _uiState.asStateFlow()

    init {
        refreshAccessPoints()
    }

    /**
     * Refresh access points and update the UI state
     */
    fun refreshAccessPoints() {
        viewModelScope.launch {
            val result = accessPointsRepository.scanAccessPoints()
            // Omit logic to update state
        }
    }
}

最后是我的存储库类(数据层):

class AccessPointsRepository {
    suspend fun scanAccessPoints(): List<ScanResult> {
       // How do I proceed here??
       val wifiScanReceiver: BroadcastReceiver = WifiScanReceiver() // Should I instantiate this here??
    
       // Register a broadcast listener for SCAN_RESULTS_AVAILABLE_ACTION, which is called when scan requests are completed
       registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) // This is incorrect, I need the context

       val success = wifiManager.startScan() // This won't work without initializing wifiManager, for which I need the context
    }
}

class WifiScanReceiver: BroadcastReceiver() {
    var scanResultList = mutableListOf<ScanResult>()
    lateinit var wifiManager: WifiManager // Where do I initialize this if I need the context??

    override fun onReceive(context: Context, intent: Intent) {
        val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
        if (!success) {
            // Omit
        }

        scanResultList = wifiManager.scanResults // wifiManager is not initialized
        // Where and how do I return the scanResultList??
    }
}

那么,如果我没有上下文,如何在我的

AccessPointsRepository
类中使用/创建广播接收器?它放置在那里是否合适,或者是否更适合 ViewModel?当我在
MainActivity
文件中编写所有内容时,我能够使其工作,因此问题不在于实现本身,而更多在于保持关注点分离的干净架构中的最佳方法是什么。我看到了
AndroidViewModel
有对应用程序上下文的引用,但看起来使用它不是一个好的做法。

我也不知道应该如何将

List<ScanResult>
onReceive
方法传递到我的
ViewModel
类。感觉好像我做的一些事情根本上是错误的。

有人可以提供任何指导,告诉我在遵循最佳架构原则的同时应该如何进行吗?提前谢谢大家!

android kotlin android-jetpack-compose broadcastreceiver
1个回答
0
投票

如果没有上下文,如何在 AccessPointsRepository 类中使用/创建广播接收器?

你不能,所以你需要给存储库一个

Context

  • 将存储库的 API 定义为

    interface

  • 使用依赖反转框架(Dagger/Hilt、Koin、kotlin-inject 等)为该接口的存储库实现提供

    Application

  • 让应用程序的其余部分通过从依赖倒置框架请求接口来使用存储库

  • 当您编写涉及存储库的单元测试时,使用该接口创建测试假

它放置在那里是否合适,或者是否更适合 ViewModel?

数据层中的存储库或类似的东西似乎是一个合理的选择。

我看到

AndroidViewModel

 有对应用程序上下文的引用,但看起来使用它不是一个好的做法。

我更愿意将其描述为“知道

为什么你正在使用AndroidViewModel

及其
Context
”。您很有可能希望在其他地方进行 
Context
 访问,但很可能有一些充分的理由不时在视图模型中使用它。

我也不知道应该如何将 List 从 onReceive 方法传递到我的 ViewModel 类。

即兴地,我会让存储库接口公开一个

Flow

。让存储库实现通过 
MutableStateFlow
 实现这一点。当您从扫描中获得结果时,用当前的 
MutableStateFlow
 更新 
List
。视图模型可以观察到
Flow
。您可以通过调用存储库上的函数来触发扫描(或稍后重新扫描,例如通过拉动刷新)。

随着时间的推移,你会变得更加成熟。例如,您可以将

List

 包装在有助于区分“我们没有接入点,因为我们还没有扫描”和“我们没有接入点,因为我们找不到任何接入点”和“我们没有接入点”的东西中。因为我们上次扫描已经是很久以前的事了,我们认为结果已经过时了”。

© www.soinside.com 2019 - 2024. All rights reserved.