当前位置: 首页 > news >正文

成都水高新区建设局官方网站桂林网站建设 腾云

成都水高新区建设局官方网站,桂林网站建设 腾云,网页设计在安阳工资多少,社交网站开发用到的技术Jetpack Compose UI架构 引言 Jetpack Compose是我职业生涯中最激动人心的事。它改变了我工作和问题思考的方式#xff0c;引入了易用且灵活的工具#xff0c;几乎可轻松实现各种功能。 早期在生产项目中尝试了Jetpack Compose后#xff0c;我迅速着迷。尽管我已有使用Co… Jetpack Compose UI架构 引言 Jetpack Compose是我职业生涯中最激动人心的事。它改变了我工作和问题思考的方式引入了易用且灵活的工具几乎可轻松实现各种功能。 早期在生产项目中尝试了Jetpack Compose后我迅速着迷。尽管我已有使用Compose创建UI的经验但对新的Jetpack Compose驱动特性的组织和架构引发了许多反复。 本文目标是分享这些经验提出可扩展、易用、易操作的架构并接受反馈以进一步改进。 免责声明本文仅涉及UI部分其余应用构建遵循经典Clean Architecture方法。假设您熟悉Jetpack Compose不深入讨论UI实现细节。 示例 为了提供具体示例让我介绍一下本文将要介绍的示范项目。我们将要构建的应用程序允许用户在不同的地标之间进行切换并导航到它们。以下是基本流程的描述 用户可以滑动浏览地点卡片查看有关地点的不同信息如地点图片、名称和评分。用户可以将地点标记/取消标记为收藏。用户可以从其位置导航并规划前往所选地点的路线。为此我们需要用户的位置权限。如果出现错误我们希望显示一个消息提示。权限只有在用户选择规划路线时才会询问。如果用户拒绝权限我们会导航到另一个屏幕位置理由屏幕。我们还希望跟踪用户与分析服务的交互。 基础知识 我对 Jetpack Compose 的最初记忆是这个方程式UI f(state)。这意味着 UI 是应用于某个状态的函数的结果。让我们简要回顾一下 Compose 和响应式 UI 的重要方面特别是关于状态处理的内容状态提升和单向数据流。 状态提升 状态提升是一种在软件开发中常用的技术尤其在 UI 编程中它将组件管理和操作状态的责任移至更高级的组件或更集中的位置。状态提升的目的是改善代码组织、可重用性和可维护性。你可以在这里了解更多关于状态提升的内容。 单向数据流 单向数据流UDF是一种设计模式其中状态向下流动事件向上流动。遵循单向数据流你可以将在 UI 中显示状态的可组合项与存储和更改状态的应用程序部分解耦。 要点是我们希望我们的 UI 组件消耗状态并发出事件。如果让我们的组件处理源自外部的事件将打破这一规则引入多个真相来源。重要的是我们引入的任何“事件”都应该基于状态。 入门 首先让我们介绍核心组件这些是我们架构的基础。 State 我们从最明显的开始即状态。状态可以是根据你的用例而定的任何内容。它可以是一个数据类包含UI可能需要的所有属性或者是一个封装接口代表所有可能的情景。无论哪种情况状态是你的组件或整个屏幕UI的“静态”表示便于轻松操作。 根据我们的要求我们有一个地点列表和一个可选的错误所以我们的状态可能是这样的 data class PlacesState(val places: ListPlace emptyList(),val error: String? null )Screen 屏幕是我们方程中的 f 函数。为了遵循状态提升模式我们需要使该组件无状态并将用户交互暴露为回调。这将使我们的屏幕具有可测试性、预览性和可重用性 我们已经有了状态基于我们的需求我们只需要处理两个用户交互。所以这就是我们的屏幕可能的样子。我们还包括了可能需要的其他组合状态所以它们被提升到了屏幕外部。 Composable fun PlacesScreen(state: PlacesState,pagerState: PagerState,onFavoritesButtonClick: (Place) - Unit,onNavigateToPlaceButtonClick: (Place) - Unit ) {Scaffold {PlacesPager(pagerState pagerState,state state,onFavoritesButtonClick onFavoritesButtonClick,onNavigateToPlaceButtonClick onNavigateToPlaceButtonClick)} }Composable fun PlacesRoute(navController: NavController,viewModel: PlacesViewModel hiltViewModel(), ) {// ... state collectionLaunchedEffect(state.error) {state.error?.let {context.showError()viewModel.dismissError()}}PlacesScreen(state uiState,onFavoritesButtonClick //..onNavigateToPlaceClick {when {permissionState.isGranted - {analyitcs.track(StartRoutePlanner)navController.navigate(RoutePlanner)}permissionState.shouldShowRationale - {analytics.track(RationaleShown)navController.navigate(LocationRationale)}else - {permissionState.launchPermissionRequest()}}}) }Route 路由(Route)是整个流程的入口。 Composable fun PlacesRoute(navController: NavController,viewModel: PlacesViewModel hiltViewModel(), ) {// ... state collectionLaunchedEffect(state.error) {state.error?.let {context.showError()viewModel.dismissError()}}PlacesScreen(state uiState,onFavoritesButtonClick //..onNavigateToPlaceClick {when {permissionState.isGranted - {analyitcs.track(StartRoutePlanner)navController.navigate(RoutePlanner)}permissionState.shouldShowRationale - {analytics.track(RationaleShown)navController.navigate(LocationRationale)}else - {permissionState.launchPermissionRequest()}}}) }这是PlacesRoute函数的简化版本但已经相当庞大。随着每个新的用户交互和基于状态的效果这个函数的大小将会增长使其变得更难理解和维护。另一个问题是回调函数。随着每个新的用户交互我们将不得不在PlacesScreen的声明中添加另一个回调这也可能会变得相当大。 另外让我们考虑一下测试。我们可以轻松测试屏幕和ViewModel但是Route呢它有很多内容不是每样东西都可以轻松模拟。首先它与屏幕耦合在一起所以如果没有引用它我们将无法适当地进行单元测试。将其他组件替换为存根将需要我们将所有内容移到Route的声明中。 进行改变 让我们尝试解决我们迄今为止已经确定的这些问题 Action 在看到这些回调时我脑海中首先想到的是如何将它们进行分组。而我当时所做的第一件事情是这样的 sealed interface PlacesAction {data class NavigateToButtonClicked(val place: Place) : ParcelActiondata class FavoritesButtonClicked(val place: Place) : ParcelAction }虽然这使我们能够将我们的操作分组到一个明确定义的结构中但也带来了不同的问题。 在屏幕级别上我们将不得不实例化这些类并调用我们的onAction回调。如果你熟悉重组Re-composition的工作原理当涉及到lambda表达式时你可能还会有冲动将其包裹在remember中以避免不必要的UI重新渲染。 Composable fun PlacesScreen(state: PlacesState,onAction: (PlacesAction) - Unit ) {PlacesPager(onFavoritesButtonClicked { onAction(PlacesAction.FavoritesButtonClicked(it))}) }另一方面Route还引入了另一件我不太喜欢的事情——可能是巨大的when语句。 PlacesScreen(state uiState,onAction { when(it) {FavoritesButtonClick //..NavigateToPlaceClicked {when {permissionState.isGranted - {analyitcs.track(StartRoutePlanner)navController.navigate(RoutePlanner)}permissionState.shouldShowRationale - {analytics.track(RationaleShown)navController.navigate(LocationRationale)}else - {permissionState.launchPermissionRequest()}}})所有这些都使我找到了一个更好的解决方案那就是一个简单的数据类。 data class ParcelActions(val onFavoritesClicked: (Place) - Unit {},val onNavigateToButtonClicked: (Place) - Unit {}, )这使我们能够在与屏幕相关的操作中引入相同的分组水平和便利性以及一种更简单的方式将这些操作传递给相关组件。 Composable fun PlacesScreen(state: PlacesState,actions: PlacesActions ) {PlacesPager(onFavoritesButtonClicked actions.onFavoritesButtonClicked,onNavigateToPlaceButtonClicked actions.onNavigateToPlaceButtonClicked) }现在在Route方面我们还可以避免使用when语句并引入以下实用程序以便在每次重组时不会重新创建Actions类使Route更加简洁。 Composable fun PlacesRoute(viewModel: PlacesViewModel,navController: NavController, ) {val uiState by viewModel.stateFlow.collectAsState()val actions rememberPlacesActions(navController)LaunchedEffect(state.error) {state.error?.let {context.showError()viewModel.dismissError()}}PlacesScreen(state uiState,actions actions)}Composable fun rememberPlacesActions(navController: NavController,analytics: Analytics LocalAnalytics.current,permissionState: PermissionState rememberPermissionState(), ) : PlacesActions {return remember(permissionState, navController, analytics) {PlacesActsions(onNavigateToPlaceClick {when {permissionState.isGranted - {analyitcs.track(RoutePlannerClicked)navController.navigate(RoutePlanner)}permissionState.shouldShowRationale - {analytics.track(RationaleShown)navController.navigate(LocationRationale)}else - {permissionState.launchPermissionRequest()}}})} }虽然PlacesRoute现在更加直观但我们所做的只是将其所有的Actions逻辑移到另一个函数中这既没有提高可读性也没有提高可扩展性。此外我们的第二个问题仍然存在——基于状态的效果。我们的UI逻辑现在也分散开来引入了不一致性并且我们并没有使其变得更具可测试性。现在是我们引入最后一个组件的时候了。 Coordinator 协调器的核心作用正如你可能从其名称中猜到的是协调不同的操作处理程序和状态提供者。协调器观察和响应状态变化并处理用户操作。你可以将其视为我们流程的Compose状态。在我们简化的示例中协调器的样子如下。 需要注意的是由于我们的协调器现在不在可组合范围内我们可以以更直接的方式处理一切无需LaunchedEffect就像我们通常在ViewModel中所做的那样只不过这里的业务逻辑是UI逻辑。 class PlacesCoordinator(val viewModel: PlacesViewModel,val navController: NavController,val context: Context,private val permissionState: PermissionState,private val scope: CoroutineScope ) {val stateFlow viewModel.stateFlowinit {// now we can observe our state and react to itstateFlow.errorFlow.onEach { error -context.toast(error.message)viewModel.dismissError()}.launchIn(scope)}// and handle actionsfun navigateToRoutePlanner() {when {permissionState.isGranted - {viewModel.trackRoutePlannerEvent()navController.navigate(RoutePlanner)}permissionState.shouldShowRationale - {viewModel.trackRationaleEvent()navController.navigate(LocationRationale)}else - permissionState.launchPermissionRequest()}}}我们的Action将修改成如下 Composable fun rememberPlacesActions(coordinator: PlacesCoordinator ) : PlacesActions {return remember(coordinator: PlacesCoordinator) {PlacesActsions(onFavoritesButtonClicked coordinator.viewModel::toggleFavorites,onNavigateToPlaceButtonClicked coordinator::navigateToRoutePlanner) }我们的Route修改如下 Composable fun PlacesRoute(coordinator: PlacesCoordinator rememberPlacesCoordinator() ) {val uiState by coordinator.stateFlow.collectAsState()val actions rememberPlacesActions(coordinator)PlacesScreen(state uiState,actions actions)}在我们的示例中PlacesCoordinator 现在负责在我们的功能流中发生的UI逻辑。由于它了解不同的状态我们可以轻松地对状态变化做出反应并为每个用户交互构建条件逻辑。如果交互很直接我们可以轻松地将其委托给相关的组件比如 ViewModel。 通过拥有协调器我们还可以控制向屏幕公开哪些状态。如果我们有多个 ViewModel 或者 ViewModel 状态对于我们正在处理的屏幕来说过于庞大我们可以将这些状态组合起来或者公开部分状态。 val screenStateFlow viewModel.stateFlow.map { PartialScreenState() }// orval screenStateFlow combine(vm1.stateFlow, vm2.stateFlow) { ScreenStateFlow() }另一个好处是整个流程的UI逻辑现在与Route解耦这意味着我们可以将我们的Coordinator作为另一个Route的一部分使用而无需复制重要内容并保持屏幕部分无状态。 Composable fun TwoPanePlacesRoute(detailsCoordinator: PlacesDetailsCoordinator,placesCoordinator: PlacesCoordinator ) {TwoPane(first {PlacesScreen(state placesCoordinator.state,actions rememberPlacesActions(placesCoordinator))},second {PlaceDetailsScreen(state detailsCoordinator. state,actions rememberDetailsActions(detailsCoordinator))}) }最后现在我们可以通过测试实现它的组件来测试我们的UI逻辑。让我们看看如何通过使用我们的“当权限被拒绝时导航到理由屏幕”来测试我们的Coordinator。 这部分假设您对如何测试Composable组件有一些了解。 fun test_NavigateToRatinoleIfPermissionWasDeniedBefore() {composeRule.setContent {// 1ComposableUnderTest(coordinator rememberPlacesCoordinator(navController testNavController,viewModel NearbyPlacesViewModel()))}// 2composeRule.onNode(hasText(Navigate)).performClick()// 3Assert.assertEquals(locationRationale,navController.currentBackStackEntry?.destination?.route) }让我们快速浏览一下这个测试 首先我们发出了作为测试对象的Composable UI。这个UI的结构很简单直接调用了我们的Coordinator。 Composable private fun ComposableUnderTest(coordinator: NearbyPlacesCoordinator) {NavHost(navController coordinator.navController,startDestination home) {composable(home) {Button(onClick { coordinator.navigateToPlace(Place.Mock) }) {Text(text Navigate) }}composable(locationRationale) {Text(text No permission)}} } 其次我们以编程方式点击“导航”按钮触发操作并让Coordinator处理它。最后我们通过检查NavHostController中的当前目标是否与我们预期的目标一致来验证我们的假设是否有效我们的实现是否正常工作。 总结一下我们进行的重构和取得的成就 我们的Screen仍然完全是无状态的。它仅依赖于作为函数参数传递的内容。所有用户交互都通过Actions暴露给其他组件处理。Route现在在导航图中充当简单的入口点。它收集状态在重新组合过程中记住我们的操作。Coordinator现在正在做大部分繁重的工作响应状态变化并将用户交互委派给其他相关组件。它完全与Screen和Route解耦可以在另一个路由中重用并单独测试。 以下图表展示了我们现在拥有的数据流程。 问答时间 每个Compose屏幕都需要一个协调器吗 简短的回答是这取决于情况对于一个非常简单的流程比如一个带有两个操作的对话框可能有点过于复杂。你可能会完全取消操作数据类将这些操作放在路由中处理。对于一个随着时间复杂度增加的屏幕我认为从一开始就值得投资或者在看到路由增长时开始进行重构。 LaunchedEffect 是否已被“弃用” 当然没有同样一个没有协调器的简单屏幕可以使用LaunchedEffect来对状态变化做出反应这是完全可以的。当UI逻辑存在于屏幕层并在屏幕层中终止时您仍然可以在屏幕中使用LaunchedEffect例如动画。 路由没有做太多事情 是的在我们的示例中路由在责任方面相当轻量级。但将其作为导航入口意味着更多。许多不是基于状态的效果都属于路由的处理范畴。例如我们可以使用SideEffect来调整颜色或者放置BackHandler来拦截返回按钮的按下这在屏幕内并不总是合适。 协调器会像路由一样随着时间而增长吗 很可能是的。这可能是它正在做太多事情的迹象其中一些事情可以提取到另一个具有状态的组件中甚至是另一个协调器中。就像您从屏幕中提取不同的UI组件来封装一些UI一样您可以构建其他组件或协调器来封装UI逻辑。 资源 Jetpack Compose UI Architecture IDE Pluginhttps://plugins.jetbrains.com/plugin/19034-jetpack-compose-ui-architecture-templates compose ui架构文档https://levinzonr.github.io/compose-ui-arch-docs/
http://www.pierceye.com/news/185779/

相关文章:

  • 网站开发亿码酷技术网站建设选谋者
  • 智能家居网站模板怎样做网站标题优化
  • 深圳制作网站制作公司哪家好最简洁 wordpress主题
  • 重庆忠县网站建设公司推荐国内公关公司
  • 给彩票网站做代理违法吗wordpress文章与页面关联
  • 网站标题加后缀模拟ip访问网站
  • 临清网站建设费用什么是网络营销的基础
  • 街道办的网站由谁做的企业首次建设网站的策划流程
  • 优化大师免费版下载一键优化下载安装
  • 网站建设近五年出版的书籍甘肃省工程建设信息官方网站
  • 杭州网站现场备案项目营销策划方案
  • 网站打包成app软件php网站 上传
  • 行业网站建设策划方案系部网站开发计划书
  • 建设部网站投诉核查做网站一般几个人
  • 360网站推广官网网址怎样在网站做咨询医生挣钱
  • 重庆市建设银行网站一站式网站建设有哪些
  • 自学设计软件的免费网站免费ppt模板简约
  • 申请个人网站怎么申请网站类型有哪些
  • 做网站推广托管注意哪个网站做推广好
  • 大竹网站建设泗阳城乡建设局网站
  • 山东省住房和城乡建设厅服务网站做网站的注意点
  • 网站排名优化软件江西网站备案要求
  • 桐柏县建设局网站邢台建设局网站
  • 网站域名变更怎么查英国做网站的人
  • 嘉兴公司的网站设计wordpress 送女友
  • 10个免费的黑科技网站电子元器件商城网站建设
  • 动画型网站做免费推广的平台
  • 购物网站怎么创建深圳地区5g微波网站建设计划
  • 安做省民改厅网站网站带支付模板
  • 做什么网站吸引人建设网站模板