繁体   English   中英

Jetpack Compose 状态提升、预览和 ViewModels 最佳实践

[英]Jetpack Compose State Hoisting, Previews, and ViewModels best practices

因此,似乎 Jetpack Compose 中推荐的做法是从可组合项中提升状态,使它们无状态、可重用和可测试,并允许在预览中轻松使用它们。 所以,而不是有类似的东西

@Composable
fun MyInputField() {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it })
}

你会像这样提升状态

@Composable
fun MyInputField(text: String, onTextChange: (String) -> Unit) {
    TextField(value = text, onValueChange = onTextChange)
}

这很好,但是一些更复杂的用途呢? 让我们假设我有一个由可组合表示的屏幕,在 View 和 ViewModel 之间有多个交互。 这个屏幕被分成多个内部可组合(例如,一个用于标题,一个用于正文,然后又分为几个较小的组合)

  • 您无法在可组合中创建 ViewModel(至少使用viewModel() ,您可以手动实例化一个)并在Preview使用此可组合(预览不支持像这样创建视图模型)
  • 在内部组合物中使用 ViewModel 会使它们有状态,不是吗?

所以我看到的“最干净”的解决方案是仅在最高可组合级别实例化我的视图模型,然后仅将代表状态的val传递给子组件,并回调到 ViewModel 函数。

但这很疯狂,我没有将所有 ViewModel 状态和函数通过各个参数传递给所有需要它们的组合。 例如,将它们分组在一个data class中可能是一个解决方案

data class UiState(
  val textInput: String,
  val numberPicked: Int,
  ……

也许为回调创建另一个? 但这仍然创建了一个全新的类,只是为了模仿视图模型已经拥有的东西。

我实际上没有看到这样做的最佳方式是什么,而且我在任何地方都找不到

Jetmagic 是一个开源框架,它正好解决了这个问题,同时也解决了 Google 在开发 Compose 时忽略的其他主要问题。 关于您的请求,您根本不将视图模型作为参数传递。 Jetmagic 遵循“提升状态”模式,但它会为您管理视图模型并使它们与您的可组合项相关联。 它以类似于旧视图系统处理 xml 布局的方式将可组合项视为资源。 您不是直接调用可组合函数,而是要求 Jetmagic 的框架为您提供与设备配置最匹配的可组合函数的“实例”。 请记住,在旧的基于 xml 的系统下,您可以有效地为同一个屏幕设置多个布局(例如一个用于纵向模式,另一个用于横向模式)。 Jetmagic 会为您挑选合适的。 当它这样做时,它会为您提供一个对象,用于管理可组合的状态及其相关的视图模型。

您可以轻松访问屏幕层次结构中任何位置的视图模型,而无需将视图模型作为参数向下传递到层次结构。 这部分是使用 CompositionLocalProvider 完成的。

Jetmagic 旨在处理构成屏幕的顶级组合。 在可组合层次结构中,您仍然像往常一样调用可组合对象,但在有意义的地方使用状态提升。

最好的办法是下载 Jetmagic 并尝试一下。 它有一个很棒的演示,可以说明您正在寻找的解决方案:

https://github.com/JohannBlake/Jetmagic

管理复杂状态的一个好方法是将所需的复杂行为封装到一个类中并使用记住函数,同时尽可能多地使用无状态小部件,并在需要时更改状态的任何属性。

SearchTextField是一个仅使用状态提升的组件, SearchBar有后退箭头和SearchTextField ,而且它本身也是一个无状态组合。 这两者与 Searchbar 的父级之间的通信仅通过回调函数处理,这使得 SearchTextField 可重新启用并且易于在预览中使用默认状态进行预览。 HomeScreen包含此状态以及您管理更改的位置。

完整的实现发布在这里

fun <R, S> rememberSearchState(
    query: TextFieldValue = TextFieldValue(""),
    focused: Boolean = false,
    searching: Boolean = false,
    suggestions: List<S> = emptyList(),
    searchResults: List<R> = emptyList()
): SearchState<R, S> {
    return remember {
        SearchState(
            query = query,
            focused = focused,
            searching = searching,
            suggestions = suggestions,
            searchResults = searchResults
        )
    }
}

记住保持状态的功能,以便仅在组合期间进行评估。

@Composable
class SearchState<R, S>(
    query: TextFieldValue,
    focused: Boolean,
    searching: Boolean,
    suggestions: List<S>,
    searchResults: List<R>
) {
    var query by mutableStateOf(query)
    var focused by mutableStateOf(focused)
    var searching by mutableStateOf(searching)
    var suggestions by mutableStateOf(suggestions)
    var searchResults by mutableStateOf(searchResults)

    val searchDisplay: SearchDisplay
        get() = when {
            !focused && query.text.isEmpty() -> SearchDisplay.InitialResults
            focused && query.text.isEmpty() -> SearchDisplay.Suggestions
            searchResults.isEmpty() -> SearchDisplay.NoResults
            else -> SearchDisplay.Results
        }
}

并通过将状态传递给其他可组合或由 ViewModel 作为更改 UI 的任何部分的状态

fun HomeScreen(
    modifier: Modifier = Modifier,
    viewModel: HomeViewModel,
    navigateToTutorial: (String) -> Unit,
    state: SearchState<TutorialSectionModel, SuggestionModel> = rememberSearchState()
) {


    Column(
        modifier = modifier.fillMaxSize()
    ) {
            
        SearchBar(
            query = state.query,
            onQueryChange = { state.query = it },
            onSearchFocusChange = { state.focused = it },
            onClearQuery = { state.query = TextFieldValue("") },
            onBack = { state.query = TextFieldValue("") },
            searching = state.searching,
            focused = state.focused,
            modifier = modifier
        )

        LaunchedEffect(state.query.text) {
            state.searching = true
            delay(100)
            state.searchResults = viewModel.getTutorials(state.query.text)
            state.searching = false
        }

        when (state.searchDisplay) {
            SearchDisplay.InitialResults -> {

            }
            SearchDisplay.NoResults -> {

            }

            SearchDisplay.Suggestions -> {

            }

            SearchDisplay.Results -> {
 
            }
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM