Why is MediumTopAppBar (and Large) showing two TextField in compose?

Viewed 28

I am trying to make the title of a screen editable.

MediumTopAppBar(
    title = {
        val name: String? = "Some Title"
        var input by remember { mutableStateOf(name ?: "") }
        when (state.isEditingTitle) {
            true ->
                TextField(
                    value = input,
                    onValueChange = { input = it },
                    keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
                    keyboardActions = KeyboardActions(onDone = {
                        callbacks.onEditTitleChange(editTitle = false, updatedTitle = input)
                    })
                )
            false -> {
                Text(
                    modifier = Modifier.clickable { callbacks.onEditTitleChange(true, null) },
                    text = name ?: "(No Title)"
                )
            }
        }
    },
... more app bar parameters
}

When I click on the title Text(...) and the view gets recomposed the AppBar shows two TextFields enter image description here

How do I ignore the top one and only show the one in the bottom, like the Text() is only shown in the bottom?

(Fyi: the two TextInputs have their own remembered state and calls the callback with their own respective value)

  • Bonus question: How do I handle the remembered state "input" so that it resets every time the onDone keyboard action is triggered? Instead of val name: String? = "Some Title" it would of course be something in the line of val name: String? = state.stateModel.title
1 Answers

I found out why it does this, but I have no idea how to solve it (except for just making my own views and placing it close by)

It's easy to see when looking at the function for the MediumTopBar

// androidx.compose.material3.AppBar.kt

@ExperimentalMaterial3Api
@Composable
fun MediumTopAppBar(
    title: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    navigationIcon: @Composable () -> Unit = {},
    actions: @Composable RowScope.() -> Unit = {},
    windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
    colors: TopAppBarColors = TopAppBarDefaults.mediumTopAppBarColors(),
    scrollBehavior: TopAppBarScrollBehavior? = null
) {
    TwoRowsTopAppBar(
        modifier = modifier,
        title = title,
        titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarMediumTokens.HeadlineFont),
        smallTitleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
        titleBottomPadding = MediumTitleBottomPadding,
        smallTitle = title, // <- this thing, right here
        navigationIcon = navigationIcon,
        actions = actions,
        colors = colors,
        windowInsets = windowInsets,
        maxHeight = TopAppBarMediumTokens.ContainerHeight,
        pinnedHeight = TopAppBarSmallTokens.ContainerHeight,
        scrollBehavior = scrollBehavior
    )
}

There's some internal state shenanigans going on, probably checking for a Text being shown in the 2nd TopAppBarLayout (more digging required to find that), but not for any other view.

TwoRowsTopAppBar and TopAppBarLayout are not public, and can't be used directly.

This is explains why, but it would be interesting to see how to solve it (still using Medium or Large -TopAppBar)

Related