android studio bumblebee Jetpack compose textfield error - kotlin

i wrote an outlinedTextField and the "it" in onValueChange does not work along the label text
it does not read and Text also displays an error.
val usernameState = rememberSaveable{ mutableStateOf(TextFieldValue) }
OutlinedTextField(
value = usernameState.value,
onValueChange = { usernameState.value = it },
label = { Text(text = "username")}
)
this is error for calling the outlinedTextfield and setting it up:
None of the following functions can be called with the arguments supplied:
public fun OutlinedTextField(value: TextFieldValue, onValueChange: (TextFieldValue) -> Unit, modifier: Modifier = ..., enabled: Boolean = ..., readOnly: Boolean = ..., textStyle: TextStyle = ..., label: (() -> Unit)? = ..., placeholder: (() -> Unit)? = ..., leadingIcon: (() -> Unit)? = ..., trailingIcon: (() -> Unit)? = ..., isError: Boolean = ..., visualTransformation: VisualTransformation = ..., keyboardOptions: KeyboardOptions = ..., keyboardActions: KeyboardActions = ..., singleLine: Boolean = ..., maxLines: Int = ..., interactionSource: MutableInteractionSource = ..., shape: Shape = ..., colors: TextFieldColors = ...): Unit defined in androidx.compose.material
public fun OutlinedTextField(value: String, onValueChange: (String) -> Unit, modifier: Modifier = ..., enabled: Boolean = ..., readOnly: Boolean = ..., textStyle: TextStyle = ..., label: (() -> Unit)? = ..., placeholder: (() -> Unit)? = ..., leadingIcon: (() -> Unit)? = ..., trailingIcon: (() -> Unit)? = ..., isError: Boolean = ..., visualTransformation: VisualTransformation = ..., keyboardOptions: KeyboardOptions = ..., keyboardActions: KeyboardActions = ..., singleLine: Boolean = ..., maxLines: Int = ..., interactionSource: MutableInteractionSource = ..., shape: Shape = ..., colors: TextFieldColors = ...): Unit defined in androidx.compose.material

You need to provide a String as a value with the signature that you are using there. Hence, the value parameter that you are passing should be unserNameState.value.value. The first value is to extract state value from MutableState, and the second is to extract the String from TextFieldValue. Also, you need to initialize the constructor of the TextFieldValue there by adding parenthesis to it.
My recommendation would be you go for the simplest approach.
var uns by remeberSaveable{ mutableStateOf("") } // Use 'by' to treat this state as a regular variable
OutlinedTextField(
value = uns, // As simple as that
onValueChange = { uns = it },
label = { Text("User Name") }
)
Consider taking the codelabs to gain a better overall understanding of Compose.
What have you sculpted on the Moon so far?

You can do it using remember as
val usernameState = remember { mutableStateOf(TextFieldValue) }
OutlinedTextField(
value = usernameState.value,
onValueChange = { usernameState.value = it },
label = { Text(text = "username")}
)
or you can use Kotlin delegation as
val username by remember { mutableStateOf(TextFieldValue) }
OutlinedTextField(
value = username,
onValueChange = {username = it},
label = {Text(text = "username")}
)
MutableState doesn't have getters and setters by default. To use Kotlin delegation, you have to import the necessary getters and setters (suggested by IDE) which are extension function for MutableState

Related

Overflow alignment in innerTextField in kotlin jetpack compose?

I have a similar search bar I pulled out from this other stackoverflow question. I, however changed lots of it and ended up with a much simpler SearchTextField composable.
#Composable
fun SearchTextField(
text: String,
placeholder: String,
modifier: Modifier = Modifier,
onTextChange: (String) -> Unit = { },
onIconClick: () -> Unit = { },
onFocusChange: (Boolean) -> Unit = { }
) {
val focusRequester = remember { FocusRequester() }
val isTextEmpty = text.isEmpty()
TextField(
value = text,
maxLines = 1,
singleLine = true,
placeholder = { Text(text = placeholder, overflow = TextOverflow.Visible) },
onValueChange = onTextChange,
shape = RoundedCornerShape(percent = 50),
trailingIcon = { IconDecoration(isTextEmpty = isTextEmpty, onClick = onIconClick) },
colors = TextFieldDefaults.textFieldColors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent),
modifier = modifier
.wrapContentHeight()
.padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp)
.onFocusChanged { onFocusChange(it.isFocused) }
.focusRequester(focusRequester))
}
#Composable
fun IconDecoration(
isTextEmpty: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier) {
IconButton(onClick = onClick, modifier = modifier) {
Icon(
imageVector = when {
isTextEmpty -> { Icons.Default.Search }
else -> { Icons.Default.Close }
},
contentDescription = null,
tint = Color.DarkGray)
}
}
And from the the previews I get, it looks just as expected:
However, when I try to implement it on a TopAppBar title, it looks like the inner text field is not vertically aligned correctly. Below is an example of the implementation and its preview. I noticed this happens due to the SearchTextField padding modifier.
I do need that padding, but there is nowhere to set that in my current TextField, since the current modifier affects the whole composable. Where am I wrong?
#Composable
fun TopBar(
state: TopBarState,
modifier: Modifier = Modifier
) {
TopAppBar(
modifier = modifier,
title = {
SearchTextField(
text = state.query,
placeholder = "Search",
onTextChange = { query ->
state.query = query
state.suggestions = state.onSearchSuggestions(query)
},
onFocusChange = { state.isSearchFocused = it },
onIconClick = { state.query = "" },
modifier = Modifier
.padding(vertical = 8.dp) // This is causing the overflow
.wrapContentHeight(Alignment.Top)) // This won't work
},
navigationIcon = {
NavigationIcon(
isSearchFocused = state.isSearchFocused,
onMenuClick = state.onMenuClick,
onBackClick = { state.isSearchFocused = false })
},
actions = {
ActionItems(
pages = arrayListOf(Page.Notifications),
onNavigate = state.onNavigate)
})
}
#Composable
fun NavigationIcon(
isSearchFocused: Boolean,
onMenuClick: () -> Unit,
onBackClick: () -> Unit
) {
if(isSearchFocused) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null)
}
} else {
IconButton(onClick = onMenuClick) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null)
}
}
}
#Composable
fun ActionItems(
pages: List<Page>,
onNavigate: (Page) -> Unit
) {
pages.forEach { page ->
IconButton(onClick = { onNavigate(page) }) {
Icon(
imageVector = page.icon,
contentDescription = null)
}
}
}

Jetpack Compose - layouting reusable components

for practicing with reusable components in Jetpack Compose, I started a little exercise.
See picture below.
As I imagine the green row, the input row, and the rows between have the same construction.
The first element got the available space, the second takes 50.dp, and the last one got 70.dp.
I tried to seperate the width into variables an pass this vars as modifiers to the single elements in the row. I thought if I need additionally fields, the I can extend it whitout any problem.
CODE DOESN'T WORK!
#Composable
fun groundComponent(
modifier: Modifier = Modifier,
spaceBetween: Dp = 0.dp,
color: Color,
content: #Composable () -> Unit
) {
Surface(
color = color
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(spaceBetween)
) {
content()
}
}
}
#Composable
fun inputSection() {
val firstRowWidth = 1F
val secondRowWidth = 70.dp
val thirdRowWidth = 50.dp
Text("Add Ingredient")
groundComponent(color = Color.Green){
Text( text="Ingredient", modifier = Modifier.weight(firstRowWidth ))
Text( text="Amount", modifier = Modifier.widthIn(secondRowWidth ))
Text( text="Unit", modifier = Modifier.widthIn(thirdRowWidth ))
}
groundComponent{
Text( text="Sugar", modifier = Modifier.weight(firstRowWidth ))
Text( text="500", modifier = Modifier.widthIn(secondRowWidth ))
Text( text="gr", modifier = Modifier.widthIn(thirdRowWidth ))
}
groundComponent{
Text( text="Carrot", modifier = Modifier.weight(firstRowWidth ))
Text( text="1.5", modifier = Modifier.widthIn(secondRowWidth ))
Text( text="kg", modifier = Modifier.widthIn(thirdRowWidth ))
}
groundComponent{
TextField(
value = "newIngredient",
onValueChange = {},
modifier = Modifier.weight(firstRowWidth ))
TextField(
value = "newAmount",
onValueChange = {},
modifier = Modifier.widthIn(secondRowWidth )
)
TextField(
value = "newUnit",
onValueChange = {},
modifier = Modifier.widthIn(thirdRowWidth )
)
}
Button(onClick={}){Text("add")}
}
I got several errors with the .weight modifier.
So how is the right aproach to solve such a situation.
Thanks!
Modifier.weight is a Modifier that defined in specific scopes such as RowScope and ColumnScope. To be able to use modifiers that are defined in specific scopes you need to add Receiver to your content. BoxScope as Modifier.align() that is defined for instance, you can define your scopes either.
#Composable
fun GroundComponent(
modifier: Modifier = Modifier,
spaceBetween: Dp = 0.dp,
color: Color=Color.Unspecified,
content: #Composable RowScope.() -> Unit
) {
Surface(
color = color
) {
// Can't call content here because it has RowScope as receiver
// content()
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(spaceBetween)
) {
content()
}
}
}
Also in InputSection you define weight fractions as
val firstRowWidth = 1F
val secondRowWidth = 70.dp
val thirdRowWidth = 50.dp
these values should be proportionate to each other
if you set 1/5/6 for instance. or between 0f-1f
And by convention you can name Composable with capital initial letter since they are considered as widgets.
Thanks for your reply and your pretty good explanation!
With your help I solved my problem this way.
#Composable
fun InputRowGroundComponent(
modifier: Modifier = Modifier,
spaceBetweenElements: Dp = 0.dp,
color: Color,
content: #Composable RowScope.() -> Unit
) {
Surface(
color = color
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(spaceBetweenElements),
verticalAlignment = Alignment.CenterVertically
) {
content()
}
}
}
#Composable
fun OverviewHeader(
modifier: Modifier = Modifier,
text: String
) {
Text(
modifier = modifier,
text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center
)
}
#Composable
fun OverviewContent(
modifier: Modifier = Modifier,
text: String
) {
Text(
modifier = modifier,
text = text,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
#Preview(showBackground = true, widthDp = 460)
#Composable
fun testPrev() {
val rowWeights = listOf(6F,3F,2F)
val rowSpacing = 8.dp
val indentation = 10.dp
Column(
modifier = Modifier.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(rowSpacing)
) {
InputRowGroundComponent(
modifier = Modifier.heightIn(45.dp),
spaceBetweenElements = rowSpacing,
color = Color.Green
) {
OverviewHeader(text = "Ingredient", modifier = Modifier.weight(rowWeights[0]))
OverviewHeader(text = "Amount", modifier = Modifier.weight(rowWeights[1]))
OverviewHeader(text = "Unit", modifier = Modifier.weight(rowWeights[2]))
}
InputRowGroundComponent(
modifier = Modifier.heightIn(30.dp),
spaceBetweenElements = rowSpacing,
color = Color.Unspecified
) {
OverviewContent(text = "Sugar", modifier = Modifier.weight(rowWeights[0]).padding(start=indentation))
OverviewContent(text = "500", modifier = Modifier.weight(rowWeights[1]).padding(start=indentation))
OverviewContent(text = "gr", modifier = Modifier.weight(rowWeights[2]).padding(start=indentation))
}
InputRowGroundComponent(
modifier = Modifier.heightIn(30.dp),
spaceBetweenElements = rowSpacing,
color = Color.Unspecified
) {
OverviewContent(text = "Carrot", modifier = Modifier.weight(rowWeights[0]).padding(start=indentation))
OverviewContent(text = "1.5", modifier = Modifier.weight(rowWeights[1]).padding(start=indentation))
OverviewContent(text = "kg", modifier = Modifier.weight(rowWeights[2]).padding(start=indentation))
}
InputRowGroundComponent(
spaceBetweenElements = rowSpacing,
color = Color.Unspecified
) {
TextField(value = "", onValueChange = {}, modifier = Modifier.weight(rowWeights[0]))
TextField(value = "", onValueChange = {}, modifier = Modifier.weight(rowWeights[1]))
TextField(value = "", onValueChange = {}, modifier = Modifier.weight(rowWeights[2]))
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { /*Todo*/ },
content = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Add Ingredient"
)
Text(
text = "Add"
)
}
}
)
}
}
Is this approach now right?

Jetpack Compose First Try - Correct Usage and what can I do better?

I build a really small practicing application, but I found some Questions.
package com.example.jetrecept
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.sharp.Add
import androidx.compose.material.icons.sharp.Delete
import androidx.compose.material.icons.sharp.Edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.example.jetrecept.ui.theme.JetReceptTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetReceptTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainContent()
}
}
}
}
}
#Preview(showBackground = true)
#Composable
fun MainContent() {
var recipeTitle by remember { mutableStateOf("") }
val ingredientList = remember { mutableStateListOf<Ingredient>() }
var amount by remember { mutableStateOf("") } // Change to Double
var unit by remember { mutableStateOf("") }
var notation by remember { mutableStateOf("") }
RecipeContent(
recipeTitle = recipeTitle,
onRecipeTitleChange = { recipeTitle = it },
ingredientList = ingredientList,
onIngredientListUpdate = { ingredient: Ingredient, i: Int -> ingredientList[i] = ingredient},
onIngredientListAdd = { ingredientList.add(it) },
onIngredientListRemoveItem = { ingredientList.removeAt(it)},
amount = amount,
onAmountChange = { amount = it},
unit = unit,
onUnitChange = { unit = it},
notation = notation,
onNotationChange = { notation = it}
)
RecipeHeader(
recipeTitle = recipeTitle
)
}
#Composable
fun RecipeHeader(
recipeTitle: String
) {
//JetReciptTheme {
Column {
//Text(text="recipetitle: $recipeTitle") // Just for testing, if hoisting works
}
//}
}
#Composable
fun RecipeContent(
recipeTitle: String,
onRecipeTitleChange: (String) -> Unit,
ingredientList: MutableList<Ingredient>,
onIngredientListAdd: (Ingredient) -> Unit,
onIngredientListUpdate: (Ingredient, Int) -> Unit,
onIngredientListRemoveItem: (Int) -> Unit,
amount: String, // change to Double,
onAmountChange: (String) -> Unit, //change to (Double) -> Unit,
unit: String,
onUnitChange: (String) -> Unit,
notation: String,
onNotationChange: (String) -> Unit
) {
Column(modifier = Modifier
.fillMaxSize()
.padding(21.dp)) {
// Headline
Text(text="Add new recipe",
modifier = Modifier
.fillMaxWidth()
.padding(34.dp),
textAlign = TextAlign.Center)
// Title of recipe - input
SectionHeadline(title = "Recipename")
TextField(
value = recipeTitle,
onValueChange = { onRecipeTitleChange(it) },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 34.dp),
// Keyboard Action fehlt noch
)
// Ingredient - section
SectionHeadline(title = "Ingredients")
// Writing headline over the Ingredient table
if (ingredientList.isNotEmpty()) {
IngredientDescription(
modifier = Modifier.padding(bottom=4.dp),
firstColumn = "Nr.",
secondColumn = "Amount",
thirdColumn = "Unit",
fourthColumn = "Notation"
)
Divider(modifier = Modifier
.height(10.dp)
.padding(bottom = 4.dp, top = 4.dp))
}
// Print all Ingredients to an Row
ingredientList.mapIndexed { index, ingredient -> IngredientRow(
id = index,
ingredient = ingredient,
onIngredientListUpdate = onIngredientListUpdate,
onIngredientListRemoveItem = onIngredientListRemoveItem,
amount = amount,
onAmountChange = onAmountChange,
unit = unit,
onUnitChange = onUnitChange,
notation = notation,
onNotationChange = onNotationChange
)
}
// Ingredient input row
Row {
TextField(
value = amount,
onValueChange = { onAmountChange(it)},
modifier = Modifier
.width(100.dp)
.padding(end = 8.dp),
label = { Text(text="amount") },
singleLine = true
)
TextField(
value = unit,
onValueChange = { onUnitChange(it)},
modifier = Modifier
.width(80.dp)
.padding(end = 8.dp),
label = { Text(text = "unit") },
singleLine = true
)
TextField(
value = notation,
onValueChange = { onNotationChange(it)},
modifier = Modifier.weight(1F),
label = { Text(text = "notation")},
singleLine = true
)
}
Spacer(modifier= Modifier.height(8.dp))
// Add - Button, adding Ingredients
IconButton(
buttonText = "add",
icon = Icons.Sharp.Add,
iconDescription = "add icon",
enabled = amount.isNotBlank() && unit.isNotBlank() && notation.isNotBlank(),
onClick = {
onIngredientListAdd(Ingredient(amount=amount, unit=unit, notation=notation))
onAmountChange(""); onUnitChange(""); onNotationChange("")
})
}
}
#Composable
fun IngredientRow(
id: Int = 1,
ingredient: Ingredient,
onIngredientListUpdate: (Ingredient, Int) -> Unit,
onIngredientListRemoveItem: (Int) -> Unit,
amount: String,
onAmountChange: (String) -> Unit,
unit: String,
onUnitChange: (String) -> Unit,
notation: String,
onNotationChange: (String) -> Unit
) {
var buttonVisibility by remember { mutableStateOf(false)}
var showEditDialog by remember { mutableStateOf(false)}
Column( modifier = Modifier.fillMaxWidth()) {
IngredientDescription(
modifier = Modifier.clickable { buttonVisibility = !buttonVisibility },
firstColumn = "$id",
secondColumn = ingredient.amount,
thirdColumn = ingredient.unit,
fourthColumn = ingredient.notation
)
if (buttonVisibility) {
Row(modifier = Modifier
.padding(bottom = 8.dp, top = 8.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
IconButton(
buttonText = "edit",
icon = Icons.Sharp.Edit,
iconDescription = "edit icon",
onClick = {
onAmountChange( ingredient.amount)
onUnitChange( ingredient.unit)
onNotationChange( ingredient.notation)
showEditDialog = true}
)
IconButton(
buttonText = "delete",
icon = Icons.Sharp.Delete,
iconDescription = "delete icon",
onClick = { onIngredientListRemoveItem(id) }
)
}
}
Divider(modifier = Modifier
.height(10.dp)
.padding(bottom = 4.dp, top = 4.dp))
if (showEditDialog) {
AlertDialog(
onDismissRequest = {
onAmountChange(""); onUnitChange(""); onNotationChange("")
showEditDialog = false},
title = {Text(text="Edit ingredient")},
text = {
Column {
Text(text="Amount:")
TextField(value = amount, onValueChange = { onAmountChange(it) })
Text(text="Unit:")
TextField(value = unit, onValueChange = { onUnitChange(it) })
Text(text="Notation:")
TextField(value = notation, onValueChange = { onNotationChange(it) })
}
},
confirmButton = { onIngredientListUpdate(
Ingredient(amount= amount, unit= unit, notation= notation), id)
Button(onClick = {
showEditDialog = false
buttonVisibility = false
onAmountChange(""); onUnitChange(""); onNotationChange("")}) {
Text(text = "apply")
}
},
dismissButton = {
Button(onClick = {
showEditDialog = false
buttonVisibility = false
onAmountChange(""); onUnitChange(""); onNotationChange("")}) {
Text(text = "discard")
}
}
)
}
}
}
#Composable
fun SectionHeadline(title: String) {
Text(text = title)
Divider(modifier = Modifier.height(2.dp))
Spacer(modifier = Modifier.height(8.dp))
}
#Composable
fun IngredientDescription(
modifier: Modifier = Modifier,
firstColumn: String, secondColumn: String, thirdColumn: String, fourthColumn:String) {
Row( modifier = modifier
.fillMaxWidth()
.padding(bottom = 4.dp, top = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = firstColumn, modifier = Modifier.width(30.dp), textAlign = TextAlign.Center)
Text(text = secondColumn, modifier = Modifier.width(60.dp), textAlign = TextAlign.Center)
Text(text = thirdColumn, modifier = Modifier.width(50.dp), textAlign = TextAlign.Center)
Text(text = fourthColumn, modifier = Modifier.weight(1F))
}
}
#Composable
fun IconButton(
buttonText: String,
icon: ImageVector,
enabled: Boolean = true,
iconDescription: String,
onClick: () -> Unit
) {
Button(onClick = onClick, enabled = enabled) {
Text(text=buttonText, modifier = Modifier.padding(end=5.dp))
Icon(imageVector = icon, contentDescription = iconDescription)
}
}
// data class Ingredient (var amount: Double, var unit: String, var notation: String)
data class Ingredient (var amount: String, var unit: String, var notation: String)
Is the way of hoisting correct? I mean, I take most of the states out of the Composables but some states inside are just for internal working. Should I also take this out of the Composable to make it complete stateless (buttonVisibility and showEditDialog)?
Are there any things I can do better?

Jetpack Compose - Disable TextField long press handler

I have an IconButton in the trailingIcon of OutlinedTextField like:
OutlinedTextField(
modifier = Modifier.weight(1f),
label = { Text(text = "Label") },
value = text,
onValueChange = { text = it },
trailingIcon = {
IconButton2(onClick = {
println("onClick")
}, onLongClick = {
println("onLongClick shows TextToolbar")
}) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
}
)
IconButton2 is just a copy of IconButton but with combinedClickable to include onLongClick instead of clickable.
The problem is that when I long click IconButton2, it shows the TextToolbar for the TextField. Doesn't matter what I do, the text field will handle long click, show the TextToolbar and provide haptic feedback.
Even if I use pointerInput with awaitPointerEvent and consumeAllChanges (like here) it still triggers it. The TextField doesn't answer to any tap or anything but if I long click it, it answers!
The workaround I'm doing for now is wrapping the text field in a Row and add the IconButton beside it instead of "inside" but I needed to have the icon button as the trailingIcon.
Is there any way to properly do it?
Compose 1.0.3 and 1.1.0-alpha05 both behaves the same.
I ended up making a small hack that seems to be working fine, so basically I add a dummy Box as the trailingIcon to get the position of it, then I add an IconButton outside of it (both wrapped in a Box) and I also get the position of it + I offset it using the position of the dummy box. Not the ideal solution but works fine.
Here's the full source if anyone needs it:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colors.background
) {
var text by remember { mutableStateOf("") }
var trailingIconOffset by remember { mutableStateOf(Offset.Zero) }
var iconButtonOffset by remember { mutableStateOf(Offset.Zero) }
val colors = TextFieldDefaults.outlinedTextFieldColors()
Column {
//With hack
Box {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "With hack") },
value = text,
onValueChange = { text = it },
trailingIcon = {
Box(modifier = IconButtonSizeModifier
.onGloballyPositioned {
trailingIconOffset = it.positionInRoot()
}
)
},
colors = colors
)
val contentColor by colors.trailingIconColor(
enabled = true,
isError = false
)
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalContentAlpha provides contentColor.alpha
) {
IconButton2(
modifier = Modifier
.onGloballyPositioned {
iconButtonOffset = it.positionInRoot()
}
.absoluteOffset {
IntOffset(
(trailingIconOffset.x - iconButtonOffset.x).toInt(),
(trailingIconOffset.y - iconButtonOffset.y).toInt()
)
},
onClick = {
text = "onClick"
},
onLongClick = {
text = "onLongClick"
}
) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
}
}
}
//Without hack
Box {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "Without hack") },
value = text,
onValueChange = { text = it },
trailingIcon = {
IconButton2(
onClick = {
text = "onClick"
},
onLongClick = {
text = "onLongClick"
}
) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = null
)
}
},
colors = colors
)
}
}
}
}
}
}
}
private val RippleRadius = 24.dp
private val IconButtonSizeModifier = Modifier.size(48.dp)
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun IconButton2(
modifier: Modifier = Modifier,
onClick: () -> Unit,
onLongClick: (() -> Unit)? = null,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: #Composable () -> Unit
) {
Box(
modifier = modifier
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = RippleRadius)
)
.then(IconButtonSizeModifier),
contentAlignment = Alignment.Center
) {
val contentAlpha = if (enabled) LocalContentAlpha.current else ContentAlpha.disabled
CompositionLocalProvider(LocalContentAlpha provides contentAlpha, content = content)
}
}

DecorationBox in Jetpack Compose BasicTextField

I use jetpack compose create a editText,I want to show a hint like before "android:hint",
so I try to use decorationBox,but after I created it the input isn't displayed and the log can display my input content.
this is my code,
val passState= remember { mutableStateOf(TextFieldValue("")) }
BasicTextField(
decorationBox = {
Text("password",color = loginGrayColor)
},
value = passState.value,
onValueChange = { passState.value = it ; Log.d("password",it.text) },
singleLine = true,
maxLines = 1,
textStyle = TextStyle(
fontSize = 15.sp,
color = loginInputTextColor
),
modifier = Modifier
.padding(start = 10.dp, top = 10.dp)
.height(20.dp)
)
You have to add the innerTextField provided by the decorationBox.
Something like:
var value by remember { mutableStateOf(TextFieldValue("")) }
BasicTextField(
value = value,
onValueChange = { value = it },
decorationBox = { innerTextField ->
Row(
Modifier
.background(Color.LightGray, RoundedCornerShape(percent = 30))
.padding(16.dp)
) {
if (value.text.isEmpty()) {
Text("Label")
}
innerTextField() //<-- Add this
}
},
)
If you would like to have the cursor start at the beginning of the placeholder label, put the decorationBox content inside a Box rather than a Row.