I have a Composable with a Boxand some text and it also returns a value
How to use that value
#Composable
fun dummyAndUselessUI(String:String) : String{
val text = remember { mutableStateOf("") }
Box(modifier = Modifier.size(100.dp)){ Text(String) }
return text.value
}
You don't need a function that return a value, in Compose you handle State
#Composable
fun dummyScreen() {
var text by rememberSaveable { mutableStateOf("") }
dummyAndUselessUI(text = text, onNameChange = { text = it })
}
#Composable
fun dummyAndUselessUI(text: String, onTextChange: (String) -> Unit) {
Box(modifier = Modifier.size(100.dp)){
OutlinedTextField(
value = text,
onValueChange = onTextChange,
label = { Text("Name") }
)
}
}
Related
How can I get the transformed value from VisualTransformation in Jetpack compose TextField? since the only thing that is changing is the visual text not the actual input/buffered text?
class CurrencyAmountInputVisualTransformation(
private val currencySymbol: String,
private val unbufferedValueChange: (String) -> Unit
) : VisualTransformation {
private val symbols = DecimalFormat().decimalFormatSymbols
private val numberOfDecimals: Int = 2
override fun filter(text: AnnotatedString): TransformedText {
val zero = symbols.zeroDigit
val inputText = text.text
val intPart = inputText
.dropLast(numberOfDecimals)
.reversed()
.chunked(3)
.joinToString(symbols.groupingSeparator.toString())
.reversed()
.ifEmpty {
zero.toString()
}
val fractionPart = inputText.takeLast(numberOfDecimals).let {
if (it.length != numberOfDecimals) {
List(numberOfDecimals - it.length) {
zero
}.joinToString("") + it
} else {
it
}
}
val formattedNumber = intPart + symbols.decimalSeparator.toString() + fractionPart
val value = inputText.dropLast(numberOfDecimals)
unbufferedValueChange("$value.$fractionPart")
val newText = AnnotatedString(
text = "$currencySymbol $formattedNumber",
spanStyles = text.spanStyles,
paragraphStyles = text.paragraphStyles
)
...
return TransformedText(newText, ...)
}
I'm getting duplicate re-composition because of this approach since I have 2 mutable state, one for the input and one for the value that I'm getting from a lambda callback I passed to the VisualTransformation.
#Composable
internal fun CurrencyField(
) {
val pattern = remember { Regex("^\\d*\\.?\\d*\$") }
var input by remember { mutableStateOf("") }
var amountInput by remember { mutableStateOf(0.00) }
OutlinedTextInputField(
text = input,
onTextChanged = {
if (it.isEmpty() || it.matches(pattern)) {
input = it
}
},
keyboardType = KeyboardType.NumberPassword,
visualTransformation = CurrencyAmountInputVisualTransformation("PHP") {
amountInput = it.toDouble()
}
)
}
Output:
Input screenshot
Is there any way I can get
232323.23
without using a callback in the VisualTransformation class?
Thanks.
You have to apply the same filter used by the VisualTransformation
var text by remember { mutableStateOf("") }
val visualTransformation = MyVisualTransformation()
TextField(
value = text,
onValueChange = { text = it },
visualTransformation = visualTransformation
)
val transformedText = remember(text, visualTransformation) {
visualTransformation.filter(AnnotatedString(text))
}.text.text
I have a drop down spinner where i am able to get the category_title . how to get selected category_id i have created a two mutableStateOf of selected title and Id .is there any other way to get the selected category id
var mSelectedText by remember { mutableStateOf("") }
var mSelectedCategoryId by remember { mutableStateOf("") }
data class
data class MainCategory(
var category_id: String? = null,
var category_name: String? = null,
var categoryImage_url: String? = null,
var category_priority:Int? = null
)
list variable
val mCategories = mainCategories.mapTo(arrayListOf()){it.category_name}
DropDownMenu
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun MainCatDownloadContent(
mainCategories: Categories,
mainCategoryOnClick: (categoryId: String, categoryTitle:
String) -> Unit,
) {
var mExpanded by remember { mutableStateOf(false) }
val mCategories = mainCategories.mapTo(arrayListOf()){it.category_name}
var mSelectedText by remember { mutableStateOf("") }
var mSelectedCategoryId by remember { mutableStateOf("") }
var mTextFieldSize by remember { mutableStateOf(Size.Zero) }
// Up Icon when expanded and down icon when collapsed
val icon = if (mExpanded)
Icons.Filled.KeyboardArrowUp
else
Icons.Filled.KeyboardArrowDown
Column(Modifier.padding(20.dp)) {
OutlinedTextField(
value = mSelectedText, onValueChange = { mSelectedText = it },
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
mTextFieldSize = coordinates.size.toSize()
},
readOnly = true,
label = { Text(text = "Select MainCategory")},
trailingIcon = {
Icon(icon,"contentDescription",
Modifier.clickable { mExpanded = !mExpanded })
}
)
mCategories.forEach{ mainCategory ->
DropdownMenuItem(onClick = {
mSelectedText = mainCategory.toString()
mExpanded = false }) {
Text(text = mainCategory.toString())
}
}
}
}
For ex if there is list of items like this
[MainCategory(category_id=11, category_name=Fruits,
categoryImage_url=https:www category_priority=1),
MainCategory(category_id=22, category_name=Vegetable ,
categoryImage_url=https:www, category_priority=2),
MainCategory(category_id=33, category_name=Greens,
categoryImage_url=https:www, category_priority=3)
if selected item lets assume is "Fruits"! how to get its category_id which is "11" and assign to the mSelectedCategoryId variable
as #Gabriele Mariotti says you need to create rememberSaveable State Just elaborating his answer
I assume mainCategories: Categories
// is a List<> so State you can by
val selectCategory by rememberSaveable {
mutableStateOf(mainCategories)
}
Other two variables which you have
var mSelectedText by remember { mutableStateOf("") }
var mSelectedCategoryId by remember { mutableStateOf("") }
Your are missing the DropDownMenu and i assume you just need to show the categoryName and get the selected id of the drop down menu from your question you can do this way
Column(Modifier.padding(20.dp)) {
OutlinedTextField(
value = mSelectedText,
onValueChange = { mSelectedText = it },
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
mTextFieldSize = coordinates.size.toSize()
},
readOnly = true,
label = { Text(text = "Select MainCategory") },
trailingIcon = {
Icon(icon, "contentDescription",
Modifier.clickable { mExpanded = !mExpanded })
}
)
DropdownMenu(expanded = mExpanded,
onDismissRequest = { mExpanded = false },
modifier = Modifier.width(with(
LocalDensity.current) {
mTextFieldSize.width.toDp()
})) {
selectCategory.forEach {
DropdownMenuItem(onClick = {
mSelectedText = it.category_name.toString()
mSelectedCategoryId = it.category_id.toString()
mExpanded = false
}) {
Text(text = it.category_name.toString())
}
}
}
}
Log.i(TAG,"Get the category_name: $mSelectedText and category_id: $mSelectedCategoryId" )
You can use something different.
Add the #Parcelize annotation to the object:
#Parcelize
data class MainCategory(
var category_id: String,
var category_name: String,
) : Parcelable
Then you can use a list of MainCategory and save the state of the selected MainCategory using:
#Composable
fun MainCatDownloadContent(
mainCategories: List<MainCategory>
){
var mExpanded by remember { mutableStateOf(false) }
var selectedCategory by rememberSaveable {
mutableStateOf(mainCategories[0])
}
//...
OutlinedTextField(
value = value = selectedCategory.category_name + " - " + selectedCategory.category_id, //you can access all the properties inside the selectedCategory
//..
)
//..
mainCategories.forEach{ mainCategory ->
DropdownMenuItem(
onClick = {
selectedCategory = mainCategory
mExpanded = false
}
){
//....
}
}
}
Finally in your case is better to use a ExposedDropdownMenuBox + ExposedDropdownMenu as described in this question.
Description:
It's pretty basic problem but I've tried few solutions and nothing is working for me. I simply want to save state of BasicTextField in ViewModel. I tried mutableStateOf("SampleText") and text appears in BasicTextField but is not editable, even keyboard didn't appear.
ViewModel Code:
#HiltViewModel
class NewWorkoutViewModel #Inject constructor(...) : ViewModel() {
var workoutTitle by mutableStateOf("")
...
}
Screen Code:
#Composable
fun NewWorkoutScreen(
navController: NavController,
viewModel: NewWorkoutViewModel = hiltViewModel()
) {
Scaffold(...) { contentPadding ->
LazyColumn(...) {
item {
FillInContentBox(title = "Title:") {
// TextField which is not working
BasicTextField(textStyle = TextStyle(fontSize = 20.sp),
value = viewModel.workoutTitle,
onValueChange ={viewModel.workoutTitle = it})
}
}
}
}
}
#Composable
fun FillInContentBox(title: String = "", content: #Composable () -> Unit = {}) {
Box(...) {
Column(...) {
Text(text = title)
content()
}
}
}
Second attempt:
I tried also using State Flow but still, I can't edit (fill in) text into TextField.
ViewModel Code:
#HiltViewModel
class NewWorkoutViewModel #Inject constructor(...) : ViewModel() {
private val _workoutTitle = MutableStateFlow("")
var workoutTitle = _workoutTitle.asStateFlow()
fun setWorkoutTitle(workoutTitle: String){
_workoutTitle.value = workoutTitle
}
...
}
Screen Code:
#Composable
fun NewWorkoutScreen(
navController: NavController,
viewModel: NewWorkoutViewModel = hiltViewModel()
) {
Scaffold(...) { contentPadding ->
LazyColumn(...) {
item {
FillInContentBox(title = "Title:") {
// TextField which is not working
BasicTextField(textStyle = TextStyle(fontSize = 20.sp),
value = viewModel.workoutTitle.collectAsState().value,
onValueChange = viewModel::setWorkoutTitle)
}
}
}
}
}
#Composable
fun FillInContentBox(title: String = "", content: #Composable () -> Unit = {}) {
Box(...) {
Column(...) {
Text(text = title)
content()
}
}
}
You can do it this way:
Create variable inside your composable
var workoutTitle = remember{mutableStateOf("")}
Pass your variable value in your textfield:
TextField(
value = workoutTitle.value,
onValueChange = {
/* You can store the value of your text view inside your
view model maybe as livedata object or state anything which suits you
*/
}
)
I'm writing instrumented tests for a Jetpack Compose component. My composable uses rememberSaveable to remember between configuration changes (activity restarts):
#Composable
fun AddUserScreen() {
Input(
shouldRequestFocus = true,
stringResource(R.string.user_first_name),
stringResource(R.string.user_first_name_label),
tag = "input-first-name"
)
}
#Composable
fun Input(
shouldRequestFocus: Boolean,
text: String,
label: String,
tag: String
) {
var value by rememberSaveable { mutableStateOf("") } // <-- Important part
val focusRequester = FocusRequester()
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text)
Spacer(modifier = Modifier.width(10.dp))
TextField(
value = value,
onValueChange = { value = it },
label = { Text(label) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
modifier = Modifier
.focusRequester(focusRequester)
.testTag(tag)
)
}
if (shouldRequestFocus) {
DisposableEffect(Unit) {
focusRequester.requestFocus()
onDispose { }
}
}
}
The input value is retained when I open the app myself and rotate the device. But in the following test the input is not retained on configuration change and the test fails:
#get:Rule val composeTestRule = createAndroidComposeRule<AddUserActivity>()
#Test fun whenAConfigChangeHappensTheFirstNameInputShouldRetainItsValue() {
composeTestRule.setContent {
WorkoutLoggerTheme {
AddUserScreen()
}
}
composeTestRule.onNodeWithTag("input-first-name").performTextInput("John")
composeTestRule.activity.requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE
composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag("input-first-name").assertTextEquals("John")
}
I have this composable function that a button will toggle show the text and hide it
#Composable
fun Greeting() {
Column {
val toggleState = remember {
mutableStateOf(false)
}
AnimatedVisibility(visible = toggleState.value) {
Text(text = "Edit", fontSize = 64.sp)
}
ToggleButton(toggleState = toggleState) {}
}
}
#Composable
fun ToggleButton(modifier: Modifier = Modifier,
toggleState: MutableState<Boolean>,
onToggle: (Boolean) -> Unit) {
TextButton(
modifier = modifier,
onClick = {
toggleState.value = !toggleState.value
onToggle(toggleState.value)
})
{ Text(text = if (toggleState.value) "Stop" else "Start") }
}
One thing I didn't like the code is val toggleState = remember { ... }.
I prefer val toggleState by remember {...}
However, if I do that, as shown below, I cannot pass the toggleState over to ToggleButton, as ToggleButton wanted mutableState<Boolean> and not Boolean. Hence it will error out.
#Composable
fun Greeting() {
Column {
val toggleState by remember {
mutableStateOf(false)
}
AnimatedVisibility(visible = toggleState) {
Text(text = "Edit", fontSize = 64.sp)
}
ToggleButton(toggleState = toggleState) {} // Here will have error
}
}
#Composable
fun ToggleButton(modifier: Modifier = Modifier,
toggleState: MutableState<Boolean>,
onToggle: (Boolean) -> Unit) {
TextButton(
modifier = modifier,
onClick = {
toggleState.value = !toggleState.value
onToggle(toggleState.value)
})
{ Text(text = if (toggleState.value) "Stop" else "Start") }
}
How can I fix the above error while still using val toggleState by remember {...}?
State hoisting in Compose is a pattern of moving state to a composable's caller to make a composable stateless. The general pattern for state hoisting in Jetpack Compose is to replace the state variable with two parameters:
value: T: the current value to display
onValueChange: (T) -> Unit: an event that requests the value to change, where T is the proposed new value
You can do something like
// stateless composable is responsible
#Composable
fun ToggleButton(modifier: Modifier = Modifier,
toggle: Boolean,
onToggleChange: () -> Unit) {
TextButton(
onClick = onToggleChange,
modifier = modifier
)
{ Text(text = if (toggle) "Stop" else "Start") }
}
and
#Composable
fun Greeting() {
var toggleState by remember { mutableStateOf(false) }
AnimatedVisibility(visible = toggleState) {
Text(text = "Edit", fontSize = 64.sp)
}
ToggleButton(toggle = toggleState,
onToggleChange = { toggleState = !toggleState }
)
}
You can also add the same stateful composable which is only responsible for holding internal state:
#Composable
fun ToggleButton(modifier: Modifier = Modifier) {
var toggleState by remember { mutableStateOf(false) }
ToggleButton(modifier,
toggleState,
onToggleChange = {
toggleState = !toggleState
},
)
}