kotlin Android ViewModel与View without Context的关系

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

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

41zrol4v

41zrol4v1#

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

表示- UI层

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

数据层

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



mepcadol

mepcadol2#

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

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

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

interface NetworkService {
  fun getConnectivityState(): ConnectivityState
}

class NetworkServiceImpl(context: Context) : NetworkService{
  // TODO: Initialize a connectivity manager

  fun getConnectivityState(): ConnectivityState {
    // TODO get the actual ConnectivityState  
  }
}


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

相关问题