我是移动开发新手,我正在尝试使用 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
类。感觉好像我做的一些事情根本上是错误的。
有人可以提供任何指导,告诉我在遵循最佳架构原则的同时应该如何进行吗?提前谢谢大家!
如果没有上下文,如何在 AccessPointsRepository 类中使用/创建广播接收器?
你不能,所以你需要给存储库一个
Context
:
将存储库的 API 定义为
interface
使用依赖反转框架(Dagger/Hilt、Koin、kotlin-inject 等)为该接口的存储库实现提供
Application
它放置在那里是否合适,或者是否更适合 ViewModel?数据层中的存储库或类似的东西似乎是一个合理的选择。
我看到我更愿意将其描述为“知道
AndroidViewModel
有对应用程序上下文的引用,但看起来使用它不是一个好的做法。
为什么你正在使用AndroidViewModel
及其
Context
”。您很有可能希望在其他地方进行
Context
访问,但很可能有一些充分的理由不时在视图模型中使用它。
我也不知道应该如何将 List 从 onReceive 方法传递到我的 ViewModel 类。即兴地,我会让存储库接口公开一个
Flow
。让存储库实现通过
MutableStateFlow
实现这一点。当您从扫描中获得结果时,用当前的
MutableStateFlow
更新
List
。视图模型可以观察到
Flow
。您可以通过调用存储库上的函数来触发扫描(或稍后重新扫描,例如通过拉动刷新)。随着时间的推移,你会变得更加成熟。例如,您可以将
List
包装在有助于区分“我们没有接入点,因为我们还没有扫描”和“我们没有接入点,因为我们找不到任何接入点”和“我们没有接入点”的东西中。因为我们上次扫描已经是很久以前的事了,我们认为结果已经过时了”。