简体   繁体   English

多态 Jetpack 组合屏幕

[英]Multi-state Jetpack Compose Screen

I have screen that should display one three things: (I'm also very new to Jetpack Compose)我的屏幕应该显示三件事:(我对 Jetpack Compose 也很陌生)

A Loader() if loading An AlertDialog() if there's an error And finally, based on an int from my viewModel, a new ScreenX()如果加载一个 Loader() 如果有错误则加载一个 AlertDialog() 最后,基于我的 viewModel 中的一个 int,一个新的 ScreenX()

I make an API call in my viewModel and update the 3 state variables accordingly.我在 viewModel 中调用了 API 并相应地更新了 3 个 state 变量。 The problem is that all of the views seem to be appearing over top of each other.问题是所有视图似乎都出现在彼此之上。

If I try and return Loader() , I get an error.如果我尝试return Loader() ,则会收到错误消息。

Maybe this isn't the correct way to do this, but is there any way you can have dynamic screens in a Composable?也许这不是正确的方法,但是有什么方法可以在 Composable 中使用动态屏幕?

@Composable
fun ConnectionScreen(
    ip: String,
    viewModel: BridgeViewModel,
    navController: NavHostController
) {
    val page = viewModel.page.collectAsState()
    val loading = viewModel.loading.collectAsState()
    val error = viewModel.error.collectAsState()

    Scaffold {
        if (loading.value) {
            Loader()
        }

        error.value?.let {
            AlertDialogWithButtons(
                title = "Error",
                message = it,
                onNegativeClick = { },
                onPositiveClick = {},
            )
        }

        when (page.value) {
            0 -> Screen0() 
            1 -> Screen1() 
            2 -> Screen2()
        }
    }
}

There is this bug (I'm pretty sure we can call it that since it crashes whole app with meaningless error message) where you cannot return from the insides of a lambda inside layout composable.有这个错误(我很确定我们可以称之为它,因为它会使整个应用程序崩溃并显示毫无意义的错误消息),您无法从 lambda 内部可组合布局的内部return So if you do this:所以如果你这样做:

Column {
  if (variable){
    return 
  }
  Text("test")
}

if the variable is true - compose hard crashes.如果variable为真 - 撰写硬崩溃。 I'm trying to find if there is a bug report for that.我正在尝试查找是否有错误报告。 In the meantime - there are 2 things you can do to avoid that:与此同时 - 你可以做两件事来避免这种情况:

You can move your code to function, like this:您可以将代码移动到 function,如下所示:

@Composable
fun ConnectionScreen(
    ip: String,
    viewModel: BridgeViewModel,
    navController: NavHostController
) {
    Scaffold {
        Content(viewModel)
    }
}

@Composable
fun Content(viewModel: BridgeViewModel) {
    val page = viewModel.page.collectAsState()
    val loading = viewModel.loading.collectAsState()
    val error = viewModel.error.collectAsState()
    if (loading.value) {
            Loader()
        }

        error.value?.let {
            AlertDialogWithButtons(
                title = "Error",
                message = it,
                onNegativeClick = { },
                onPositiveClick = {},
            )
        }

        when (page.value) {
            0 -> Screen0() 
            1 -> Screen1() 
            2 -> Screen2()
        }
{

Or you can stick to if-else , like this:或者你可以坚持if-else ,像这样:

@Composable
fun ConnectionScreen(
    ip: String,
    viewModel: BridgeViewModel,
    navController: NavHostController
) {
    val page = viewModel.page.collectAsState()
    val loading = viewModel.loading.collectAsState()
    val error = viewModel.error.collectAsState()

    Scaffold {
        if (loading.value) {
            Loader()
        } else {
            error.value?.let {
                AlertDialogWithButtons(
                    title = "Error",
                    message = it,
                    onNegativeClick = { },
                    onPositiveClick = {},
                )
            }

            when (page.value) {
                0 -> Screen0() 
                1 -> Screen1() 
                2 -> Screen2()
            }
        }
    }
}

I decided to create an encapsulating data class to better represent UI state.我决定创建一个封装数据 class 以更好地表示 UI state。 It's not the best, and unfortunately not reusable (I'll need to create a new data class for every screen? Even though most will be using an API, so Loading and Error should be reusable).这不是最好的,不幸的是不可重用(我需要为每个屏幕创建一个新数据 class?即使大多数人将使用 API,所以加载和错误应该是可重用的)。

enum class ResourceState { LOADING, ERROR, LINK, FINISH }

data class CreateResourceState(
    val loading: Boolean = false,
    val error: String? = null,
    val page: Int = 0,
) {
    val state: ResourceState
        get() {
            return if (loading) ResourceState.LOADING
            else if (error != null) ResourceState.ERROR
            else if (page == 1) ResourceState.LINK
            else if (page == 2) ResourceState.FINISH
            else ResourceState.LOADING
        }
}

Then in my ViewModel然后在我的 ViewModel

private val _uiState = MutableStateFlow(CreateResourceState(loading = true))
val uiState: StateFlow<CreateResourceState> = _uiState

...

_uiState.value = CreateResourceState(error = exception.message)

...

_uiState.value = CreateResourceState(page = 1)

Finally in the UI终于在 UI

val uiState = viewModel.uiState.collectAsState()

when (uiState.value.state) {
    ResourceState.LOADING -> Loader()
    ResourceState.ERROR -> ErrorDialog()
    ResourceState.LINK -> Screen1()
    ResourceState.FINISH -> Screen2()
}

It still seems weird to me that you can only return one and only one Composable.对我来说,您只能返回一个且只有一个Composable 对我来说仍然很奇怪。 Maybe I'm too used to Flutter, where you can dynamically return any Widget at any time inside of another Widget.可能我太习惯了 Flutter,在这里你可以随时在另一个 Widget 内部动态返回任何 Widget。

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

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