我很难理解如何迁移到 Google 批准的 Android 现代开发。在 MVVM 中,“业务逻辑”应该位于 ViewModel 中,并且建议 ViewModel 的子类应该不感知 Context,但大多数 Android 类需要 Context 才能提供任何功能。例如,如果没有上下文(如 WifiManager 和 ConnectivityManager),就无法获取对系统服务的引用。
作为示例,我正在尝试为具有单个按钮的应用程序构建一个简单的架构,该按钮根据 ConnectivityManager 文档中概述的活动默认网络链接更改其图像。这个系统级操作应该在代码的“业务逻辑”区域中处理,因为它与 UI 无关。它检测网络状态,更改 ViewModel 中的一些状态数据,这应该“神奇地”更改按钮图像,因为某些 Observable 通知 UI 数据已更改并触发 Compose“重组”,从而更改按钮图像。我已经开始构建这个应用程序,我将发布到目前为止我所拥有的内容。如果 ViewModel 是应用程序状态的仲裁者,如何在没有 Context 的情况下创建 ViewModel?这看起来很矛盾。 ViewModel 摘录:
enum class ConnectivityState {
WIFI,
CELLULAR,
DISCONNECTED
}
class MainActivityViewModel(context: Context) : ViewModel() {
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
private val _uiState = MutableStateFlow(MainUIState())
val uiState: StateFlow<MainUIState> = _uiState.asStateFlow()
//TODO: how to perform system level business logic in ViewModel without Context reference?
//DESIRED: keep this 'business logic' away from UI. ViewModel should be appropriate but Context should not be used here?
private val wifiManager =
context.getSystemService(WIFI_SERVICE) as WifiManager
private val connectivityManager =
context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
/**
* a NetworkCallback which will be triggered when a change in network state is sent from the system
*/
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
_uiState.value.connectivityState = getConnectivityState()
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
_uiState.value.connectivityState = getConnectivityState()
}
override fun onLost(network: Network) {
super.onLost(network)
_uiState.value.connectivityState = getConnectivityState()
}
}
init {
connectivityManager.registerDefaultNetworkCallback(networkCallback)
_uiState.value.connectivityState = getConnectivityState()
}
fun getConnectivityState(): ConnectivityState {
//these objects represent information about the currently active network
val currentNetwork = connectivityManager.getActiveNetwork()
val capabilities = connectivityManager.getNetworkCapabilities(currentNetwork)
val linkProperties = connectivityManager.getLinkProperties(currentNetwork)
if(currentNetwork == null) {
return ConnectivityState.DISCONNECTED
}
if(wifiManager.isWifiEnabled) {
//if wifi is enabled, it should be the current active network
val isWifi = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
if(isWifi != null) {
if(isWifi) {
//the current active link is a WiFi connection
logger.debug("Current active default network is WiFi.")
return ConnectivityState.WIFI
}
}
}
val isCellular = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
if(isCellular != null) {
if(isCellular) {
logger.info("Current active default network is cellular data.")
return ConnectivityState.CELLULAR
}
}
return ConnectivityState.DISCONNECTED
}
}
data class MainUIState(
var connectivityState: ConnectivityState = ConnectivityState.DISCONNECTED
)
活动摘录:
@Composable
fun wifiComposable() {
//WiFi button
Button(
onClick = {
MainActivity.openWifiSettings(LocalContext.current)
}
) {
//TODO: this image changes between DOWN, CELLULAR, WIFI images depending on connectivity state / source
Image(
//TODO: 'StateFlow.value should not be called within composition' why?
//DESIRED BEHAVIOR: when this state value changes, recomp and change image accordingly
painter = when(viewModel.uiState.value.connectivityState) {
ConnectivityState.WIFI -> painterResource(id = R.drawable.wifi_indicator)
ConnectivityState.CELLULAR -> painterResource(id = R.drawable.cellular_indicator)
ConnectivityState.DISCONNECTED -> painterResource(id = R.drawable.down_indicator)
},
modifier = Modifier.size(40.dp),
contentDescription = "WiFi Settings")
}
}