If you run the following composable and enter a long multiline text into the textfield, you will see that as the text grows, the textfield leaves the AlertDialog.
Is there a way to fix this?
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
#Preview
#Composable
fun MyComposable() {
var text by remember {
mutableStateOf("Press enter a couple of times to see the problem")
}
AlertDialog(
onDismissRequest = { },
title = {
OutlinedTextField(
value = text,
onValueChange = { text = it },
textStyle = MaterialTheme.typography.h5,
label = { Text(text = "Text") },
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
)
},
confirmButton = {
TextButton(
onClick = {}
) {
Text("Done")
}
},
dismissButton = {
TextButton(
onClick = { }
) {
Text("Cancel")
}
}
)
}
Modifier.heightIn constrain the height of the content to be between mindp and maxdp as permitted by the incoming measurement Constraints. If the incoming constraints are more restrictive the requested size will obey the incoming constraints and attempt to be as close as possible to the preferred size.
Column {
var text by remember { mutableStateOf("some text")}
OutlinedTextField(value = text,
onValueChange = {text = it},
textStyle = MaterialTheme.typography.headlineSmall,
label = { Text(text = "Text") },
modifier = Modifier.fillMaxWidth()
.heightIn(min = 0.dp, max = 150.dp))
}
Textfield will grow to max height specified in heightIn modifier and then start scrolling.
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?
I was wondering how I would go about making a TextField always look active:
Without it actually having a default value like the TextField in the screenshot does
My current code for the TextField in the screenshot is:
TextField(
value = npcId.toString(),
onValueChange = { npcId = it.toInt() },
label = { Text("Npc id") },
enabled = true
)
So essentially you do not want the default value to display in the TextField.
If the default value is -1 in this case, the following should work:
TextField(
value = if (npcID == -1) "" else npcId.toString(),
onValueChange = { npcId = it.toInt() },
label = { Text("Npc id") },
enabled = true
)
I succeded with this way.It is here source code:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalTextInputService
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.delay
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
}
#Composable
fun MainScreen() {
Surface(
modifier = Modifier.fillMaxSize()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
AutoFocusingText()
}
}
}
#Composable
fun AutoFocusingText() {
val focusRequester = remember { FocusRequester() }
val inputService = LocalTextInputService.current
val focus = remember { mutableStateOf(true) }
val text = remember { mutableStateOf("-1")}
TextField(
value = if (text.value == "-1") "" else text.value,
onValueChange = {
text.value = it
},
label = { Text("Npc id") },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged {
if (focus.value != it.isFocused) {
focus.value = it.isFocused
if (!it.isFocused) {
inputService?.hideSoftwareKeyboard()
}
}
}
)
LaunchedEffect(true) {
delay(100)
inputService?.showSoftwareKeyboard()
focusRequester.requestFocus()
}
}
#ExperimentalComposeUiApi
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
MainScreen()
}
I am creating custom Snackbar using Jetpack Compose. But I have also implemented BottomNavBar using Scaffold. Everything works fine but only one problem is snackbar goes under the BottomNavBar. So how to move above to the snackbar over BottomNavBar?
Snackbar code:
#Composable
fun DefaultSnackbar(
snackbarHostState: SnackbarHostState,
onDismiss: () -> Unit?,
modifier: Modifier = Modifier
) {
SnackbarHost(
hostState = snackbarHostState,
modifier = modifier,
snackbar = {data ->
Snackbar(
modifier = Modifier.padding(8.dp),
content = {
Text(
text = data.message,
style = MaterialTheme.typography.body2,
color = White
)
},
action = {
data.actionLabel?.let { actionLabel ->
TextButton(
onClick = {
onDismiss()
}
) {
Text(
text = actionLabel,
style = MaterialTheme.typography.body2,
color = White
)
}
}
}
)
}
)
}
Bottom Navigation Code :-
#Composable
fun BottomNav()
{
BottomNavigation(
elevation = 15.dp,
modifier = Modifier.zIndex(5f)
) {
BottomNavigationItem(
icon = {Icon(imageVector = Icons.Filled.Search, contentDescription = "search icon", tint = Color.White)},
selected = false,
onClick = { /*TODO*/ }
)
BottomNavigationItem(
icon = {Icon(imageVector = Icons.Filled.Home, contentDescription = "search icon", tint = Color.White)},
selected = true,
onClick = { /*TODO*/ }
)
BottomNavigationItem(
icon = {Icon(imageVector = Icons.Filled.MoreVert, contentDescription = "search icon", tint = Color.White)},
selected = false,
onClick = { /*TODO*/ }
)
}
}
Calling snackbar :
#ExperimentalCoroutinesApi
#AndroidEntryPoint
class RecipeListFragment : Fragment() {
private val viewModel: RecipeListViewModel by viewModels()
private val snackbarController = SnackbarController(lifecycleScope)
#ExperimentalComposeUiApi
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ComposeView(requireContext()).apply {
setContent {
AppTheme(darkTheme = viewModel.isDarkTheme.value) {
val recipes = viewModel.recipes.value
val query = viewModel.query.value
val selectedCategory = viewModel.selectedCategory.value
val scaffoldState = rememberScaffoldState()
Scaffold(
topBar = {
SearchBar(
query = query,
onQueryChange = viewModel::onQueryChange,
selectedCategory = selectedCategory,
onSelectedCategoryChanged = viewModel::onSelectedCategoryChanged,
onToggleTheme = viewModel::onToggleTheme,
searchRecipe = {
if (viewModel.selectedCategory.value?.value == "Milk") {
snackbarController.getScope().launch {
snackbarController.showSnackbar(
scaffoldSate= scaffoldState,
message = "Milk is in Snack",
actionLabel = "Hide",
)
}
} else {
viewModel.searchRecipe()
}
}
)
},
bottomBar = { BottomNav() },
scaffoldState = scaffoldState,
snackbarHost = {scaffoldState.snackbarHostState},
) {
Box(modifier = Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize())
{
...
others items
...
CircularIndeterminateProgressBar(display = viewModel.isLoading.value)
DefaultSnackbar(
snackbarHostState = scaffoldState.snackbarHostState,
onDismiss = { scaffoldState.snackbarHostState.currentSnackbarData?.dismiss()},
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
}
}
}
}
}
I was trying to practice a little with desktop compose for kotlin and when i was trying to implement AlertDialog element I got this exception:
Exception in thread "AWT-EventQueue-0 #coroutine#3" kotlin.NotImplementedError: An operation is not implemented: not implemented
The code that im trying to run :
fun main() {
var text = mutableStateOf(0F)
var num = mutableStateOf("a")
Window(
size = IntSize(500, 500),
title = num.value,
menuBar = MenuBar(
Menu(
name = "Test",
MenuItem(
name = "Dodaj random",
onClick = {
text.value = text.value + Random.nextFloat()
if (text.value > 1F) {
text.value = 0F
num.value += "a"
}
},
shortcut = KeyStroke(Key.A)
),
MenuItem(
name = "Exit",
onClick = {
AppManager.exit()
},
shortcut = KeyStroke(Key.L)
)
)
)
) {
MaterialTheme {
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(12.dp)) {
LinearProgressIndicator(text.value.toFloat(), modifier = Modifier.align(Alignment.CenterHorizontally))
val openDialog = remember { mutableStateOf(false) }
Button(
onClick = {
openDialog.value = true
}) {
Text("Click me!")
}
if (openDialog.value) {
AlertDialog(
onDismissRequest = { openDialog.value = false },
title = { Text("Dialog title") },
text = { Text("Here is a text") },
confirmButton = {
Button(
onClick = {
openDialog.value = false
}
) { Text("Confirm butt") }
},
dismissButton = {
Button(
onClick = {
openDialog.value = false
}
) { Text("Diss butt") }
}
)
}
Button(
onClick = {
text.value += 0.1F
if (text.value > 1F) {
text.value = 0F
num.value += "a"
}
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(text.value.toString())
}
Button(
onClick = {
num.value += "a"
},
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(num.component1())
}
}
}
}
}
Only when I'm trying to click on the "Click me" button I'm getting this exception other buttons works perfectly.
My imports
import androidx.compose.desktop.AppManager
import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.KeyStroke
import androidx.compose.ui.window.Menu
import androidx.compose.ui.window.MenuBar
import androidx.compose.ui.window.MenuItem
import kotlin.random.Random
Your issue seems related to this github issue.
To solve it simply update compose dependency to 0.2.0-build132.
Complete example of my build.gradle with the correct versions:
import org.jetbrains.compose.compose
plugins {
kotlin("jvm") version "1.4.20"
id("org.jetbrains.compose") version "0.2.0-build132"
}
repositories {
jcenter()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
dependencies {
implementation(compose.desktop.currentOs)
}
compose.desktop {
application {
mainClass = "MainKt"
}
}