kotlin Android ViewModel与View without Context的关系

0s0u357o  于 2023-11-21  发布在  Kotlin
关注(0)|答案(2)|浏览(278)

我很难理解如何迁移到谷歌认可的Android现代开发。在MVVM中,“业务逻辑”应该在ViewModel中,建议ViewModel的子类应该是Context不知道的,但大多数Android类需要Context才能提供任何功能。例如,如果没有Context(如WifiManager和ConnectivityManager),则无法获取对系统服务的引用。
举个例子,我试图为一个应用构建一个简单的架构,它只有一个按钮,可以根据ConnectivityManager文档中概述的活动默认网络链接来更改其图像。这个系统级操作应该在代码的“业务逻辑”区域处理,因为它与UI无关。它检测网络状态,改变了ViewModel中的一些状态数据,这应该是神奇的。由于一些Observable通知UI数据已更改并触发ComposeRecomposition更改按钮图像,因此更改按钮图像。我已经开始构建此应用程序,我将发布到目前为止我所拥有的内容。如果ViewModel是应用程序状态的仲裁者,那么创建一个没有上下文的ViewModel?这似乎是自相矛盾的。
ViewModel摘录:

  1. enum class ConnectivityState {
  2. WIFI,
  3. CELLULAR,
  4. DISCONNECTED
  5. }
  6. class MainActivityViewModel(context: Context) : ViewModel() {
  7. private val logger: Logger = LoggerFactory.getLogger(this::class.java)
  8. private val _uiState = MutableStateFlow(MainUIState())
  9. val uiState: StateFlow<MainUIState> = _uiState.asStateFlow()
  10. //TODO: how to perform system level business logic in ViewModel without Context reference?
  11. //DESIRED: keep this 'business logic' away from UI. ViewModel should be appropriate but Context should not be used here?
  12. private val wifiManager =
  13. context.getSystemService(WIFI_SERVICE) as WifiManager
  14. private val connectivityManager =
  15. context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
  16. /**
  17. * a NetworkCallback which will be triggered when a change in network state is sent from the system
  18. */
  19. private val networkCallback = object : ConnectivityManager.NetworkCallback() {
  20. override fun onAvailable(network: Network) {
  21. super.onAvailable(network)
  22. _uiState.value.connectivityState = getConnectivityState()
  23. }
  24. override fun onCapabilitiesChanged(
  25. network: Network,
  26. networkCapabilities: NetworkCapabilities
  27. ) {
  28. super.onCapabilitiesChanged(network, networkCapabilities)
  29. _uiState.value.connectivityState = getConnectivityState()
  30. }
  31. override fun onLost(network: Network) {
  32. super.onLost(network)
  33. _uiState.value.connectivityState = getConnectivityState()
  34. }
  35. }
  36. init {
  37. connectivityManager.registerDefaultNetworkCallback(networkCallback)
  38. _uiState.value.connectivityState = getConnectivityState()
  39. }
  40. fun getConnectivityState(): ConnectivityState {
  41. //these objects represent information about the currently active network
  42. val currentNetwork = connectivityManager.getActiveNetwork()
  43. val capabilities = connectivityManager.getNetworkCapabilities(currentNetwork)
  44. val linkProperties = connectivityManager.getLinkProperties(currentNetwork)
  45. if(currentNetwork == null) {
  46. return ConnectivityState.DISCONNECTED
  47. }
  48. if(wifiManager.isWifiEnabled) {
  49. //if wifi is enabled, it should be the current active network
  50. val isWifi = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
  51. if(isWifi != null) {
  52. if(isWifi) {
  53. //the current active link is a WiFi connection
  54. logger.debug("Current active default network is WiFi.")
  55. return ConnectivityState.WIFI
  56. }
  57. }
  58. }
  59. val isCellular = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
  60. if(isCellular != null) {
  61. if(isCellular) {
  62. logger.info("Current active default network is cellular data.")
  63. return ConnectivityState.CELLULAR
  64. }
  65. }
  66. return ConnectivityState.DISCONNECTED
  67. }
  68. }
  69. data class MainUIState(
  70. var connectivityState: ConnectivityState = ConnectivityState.DISCONNECTED
  71. )

字符串
活动摘录:

  1. @Composable
  2. fun wifiComposable() {
  3. //WiFi button
  4. Button(
  5. onClick = {
  6. MainActivity.openWifiSettings(LocalContext.current)
  7. }
  8. ) {
  9. //TODO: this image changes between DOWN, CELLULAR, WIFI images depending on connectivity state / source
  10. Image(
  11. //TODO: 'StateFlow.value should not be called within composition' why?
  12. //DESIRED BEHAVIOR: when this state value changes, recomp and change image accordingly
  13. painter = when(viewModel.uiState.value.connectivityState) {
  14. ConnectivityState.WIFI -> painterResource(id = R.drawable.wifi_indicator)
  15. ConnectivityState.CELLULAR -> painterResource(id = R.drawable.cellular_indicator)
  16. ConnectivityState.DISCONNECTED -> painterResource(id = R.drawable.down_indicator)
  17. },
  18. modifier = Modifier.size(40.dp),
  19. contentDescription = "WiFi Settings")
  20. }
  21. }

41zrol4v

41zrol4v1#

在Google推荐的架构方法中,推荐至少2层。(表示层-数据层)

表示- UI层

在屏幕上显示应用程序数据的UI层。
UI的作用是在屏幕上显示应用程序数据,同时也是用户交互的主要点。无论何时数据发生更改,无论是由于用户交互(如按下按钮)还是外部输入(如网络响应),UI都应更新以反映这些更改。实际上,UI是从数据层检索的应用程序状态的可视化表示。
ViewModel被视为状态保持器。
x1c 0d1x的数据

数据层

包含应用的业务逻辑并公开应用数据的数据层。
在你的情况下,将Network状态设置为集群。我建议使用Dagger Hilt注入Context。这毕竟是业务逻辑。对数据执行操作。作为Singleton访问它是一个很好的做法。



展开查看全部
mepcadol

mepcadol2#

这就是关注点分离和理想的依赖注入发挥作用的地方。
你的视图模型应该关注为特定的视图提供正确的数据。这可以是一个小的组件,也可以是整个屏幕。
视图模型本身依赖于为它提供所需数据的其他类。这些类可以是进行Web API调用的服务,从DB加载数据或检查网络状态的系统服务。
服务应该像这样作为参数传递到视图模型中:

  1. // In this context, NetworkService is some kind of helper class that is
  2. // supposed to take care of network state related code
  3. class MainActivityViewModel(networkService: NetworkService : ViewModel() {
  4. fun getConnectivityState(): ConnectivityState {
  5. return networkService.getConnectivityState()
  6. }
  7. }

字符串
现在你的视图模型本身不需要上下文了,但是NetworkService仍然需要一个。

  1. interface NetworkService {
  2. fun getConnectivityState(): ConnectivityState
  3. }
  4. class NetworkServiceImpl(context: Context) : NetworkService{
  5. // TODO: Initialize a connectivity manager
  6. fun getConnectivityState(): ConnectivityState {
  7. // TODO get the actual ConnectivityState
  8. }
  9. }


此时,您可以将NetworkService设置为单例(在Kotlin中,您可以将class交换为object),并在Activity或Application类中对其进行初始化。
然而,建议的方法是使用依赖注入(使用Hilt)来抽象所有这些。

展开查看全部

相关问题