How to create a dynamic views in jetpack compose - android

I am trying to create a some dynamic views when the user clicks add button it starts a new set of text fields and fill the required details and add all the set to final list of array i facing a problem of how to deal with the mutable state of each text field i.e value and onValueChange the text fields are sharing same viewModel mutable state.
How to deal with the dynamic views so that only the required text field value changes instead of all similar text field changing
Ui code
#Composable
fun CombineFields(viewModel: MainContentUploadViewModel = hiltViewModel()) {
val containerTitle = viewModel.containerTitle.value
val containerAbout = viewModel.containerAbout.value
OutlinedTextField(value = containerTitle.innerStateTitle,
onValueChange = { viewModel.onEvent(MainContentEvent.ContainerTitle(it)) },
label = { Text(text = "Title") })
Spacer(modifier = Modifier.height(8.dp))
TextField(value = containerAbout.innerStateAbout,
onValueChange = { viewModel.onEvent(MainContentEvent.ContainerAbout(it)) },
modifier = Modifier.height(100.dp))
Spacer(modifier = Modifier.height(8.dp))
val options = listOf("Products", "Banners", "Categories")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf(options[0]) }
// We want to react on tap/press on TextField to show menu
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
readOnly = true,
value = selectedOptionText,
onValueChange = { },
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
options.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
selectedOptionText = selectionOption
expanded = false
}
) {
Text(text = selectionOption)
}
}
}
}
}
ViewModel
#HiltViewModel
class MainContentUploadViewModel #Inject constructor(private val useCases: UseCases) : ViewModel() {
private val _containerTitle = mutableStateOf(MainContentFieldState())
val containerTitle : State<MainContentFieldState> = _containerTitle
private val _containerAbout = mutableStateOf(MainContentFieldState())
val containerAbout : State<MainContentFieldState> = _containerAbout
private val _containerPriority = mutableStateOf(MainContentFieldState())
val containerPriority : State<MainContentFieldState> = _containerPriority
private val _selectedContent = mutableStateOf(MainContentFieldState())
val selectedContent : State<MainContentFieldState> = _selectedContent
private val _selectedTags = mutableStateOf(MainContentFieldState())
val selectedTags : State<MainContentFieldState> = _selectedTags
private val _allInnerContent = mutableStateOf(MainContentFieldState())
val allInnerContent : State<MainContentFieldState> = _allInnerContent
Data Class
data class InnerContainerItems(
val containerName:String? = null,
val containerAbout:String? = null,
val containerTags:List<String>? = null,
val containerType:String? = null,
val containerPriority:Int? = null,
)
data class MainScreenContainer(
val ScreenContainer:List<InnerContainerItems>? = null
)
can anyone help me how to move further for the dynamic fields thanks.

As i can see you are using var selectedOptionText by remember { mutableStateOf(options[0]) }
I found this https://stackoverflow.com/a/68887484/20839582. Check if it is useful as it mentions using mutableStateListOf and rememberSaveable.

Related

Get the value from VisualTransformation Jetpack Compose TextField

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

How to add compose views dynamically

I am trying to add a some text fields compose dynamically. when clicking a add button a new compose fields appear and add some required texts my problem is text fields changes for all the layouts instead of the particular one. how can use the mutableStateOf for dynamic ones
my view
val containerTitle = viewModel.containerTitle.value
val containerAbout = viewModel.containerAbout.value
OutlinedTextField(value = containerTitle.innerStateTitle,
onValueChange = { viewModel.onEvent(MainContentEvent.ContainerTitle(it)) },
label = { Text(text = "Title") })
Spacer(modifier = Modifier.height(8.dp))
TextField(value = containerAbout.innerStateAbout,
onValueChange = { viewModel.onEvent(MainContentEvent.ContainerAbout(it)) },
modifier = Modifier.height(100.dp))
Spacer(modifier = Modifier.height(8.dp))
val options = listOf("Products", "Banners", "Categories")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf(options[0]) }
// We want to react on tap/press on TextField to show menu
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
readOnly = true,
value = selectedOptionText,
onValueChange = { },
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
options.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
selectedOptionText = selectionOption
expanded = false
}
) {
Text(text = selectionOption)
}
}
}
}
viewModel
#HiltViewModel
class MainContentUploadViewModel #Inject constructor(private val
useCases: UseCases) : ViewModel() {
private val _containerTitle = mutableStateOf(MainContentFieldState())
val containerTitle : State<MainContentFieldState> = _containerTitle
private val _containerAbout = mutableStateOf(MainContentFieldState())
val containerAbout : State<MainContentFieldState> = _containerAbout
private val _containerPriority = mutableStateOf(MainContentFieldState())
val containerPriority : State<MainContentFieldState> = _containerPriority
private val _selectedContent = mutableStateOf(MainContentFieldState())
val selectedContent : State<MainContentFieldState> = _selectedContent
private val _selectedTags = mutableStateOf(MainContentFieldState())
val selectedTags : State<MainContentFieldState> = _selectedTags
private val _allInnerContent = mutableStateOf(MainContentFieldState())
val allInnerContent : State<MainContentFieldState> = _allInnerContent
fun onEvent(event: MainContentEvent){
when(event) {
is MainContentEvent.ContainerTitle -> {
_containerTitle.value = containerTitle.value.copy(innerStateTitle = event.value)
}
is MainContentEvent.ContainerAbout -> {
_containerAbout.value = containerAbout.value.copy(innerStateAbout = event.about)
}
is MainContentEvent.ContainerType -> {
_selectedContent.value = selectedContent.value.copy(selectedContent = event.value)
}
is MainContentEvent.ContainerTags -> {
_selectedTags.value = selectedTags.value.copy(selectedTagsList = event.containerTags)
}
is MainContentEvent.ContainerPriority -> {
_containerPriority.value = containerPriority.value.copy(containerPriority = event.value)
}
data class
data class InnerContainerItems(
val containerName:String? = null,
val containerAbout:String? = null,
val containerTags:List<String>? = null,
val containerType:String? = null,
val containerPriority:Int? = null,
)
data class MainScreenContainer(
val ScreenContainer:List<InnerContainerItems>? = null
)
How to solve the value of all text fields changing when one text is changed thanks
Probably system share one viewmodel to each containers, if you use separated viewmodels for them. You can try using the parent viewmodel to storage information. For this you should:
create a
data class ContainerData(
val title: String,
val description: String,
)
add to parent viewmodel
var containersData by mutableStateOf<List<ContainerData>>(emptyList())
get list from UI (if you will use delegate by and add imports, you can use field without .value when you set and get data)
val containersData = viewModel.containersData
use this data for getting information about container in the LazyColumn or simple Column by index
hand over this information to your Container

How can i get the selected Id of the DropDownMenuItem in Jetpack Compose

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.

Jetpack compose Lazy Column item state does not change when state in viewModel changes

I have debugged the app and I saw that the data in UIState changes when I try to add or remove the item, especially the isAdded field. However, even though the isAdded changes, the AddableItem does not recompose. Additionally, when I try to sort items, or try to write a query THAT WILL NOT SEND ANY API REQUEST, JUST CHANGES THE STRING IN TEXTFIELD, the UI recomposes. So UI reacts to changes in UIState. I have searched for similar issues but cannot find anything. I believe that the framework must recompose when the pointer of the filed changes, however, it does not. Any idea why this happens or solve that?
This is the viewModel:
#HiltViewModel
class AddableItemScreenViewModel#Inject constructor(
val getAddableItemsUseCase: GetItems,
val getItemsFromRoomUseCase: GetRoomItems,
val updateItemCase: UpdateItem,
savedStateHandle: SavedStateHandle) : ViewModel() {
private val _uiState = mutableStateOf(UIState())
val uiState: State<UIState> = _uiState
private val _title = mutableStateOf("")
val title: State<String> = _title
private var getItemsJob: Job? = null
init {
savedStateHandle.get<String>(NavigationConstants.TITLE)?.let { title ->
_title.value = title
}
savedStateHandle.get<Int>(NavigationConstants.ID)?.let { id ->
getItems(id = id.toString())
}
}
fun onEvent(event: ItemEvent) {
when(event) {
is ItemEvent.UpdateEvent -> {
val modelToUpdate = UpdateModel(
id = event.source.id,
isAdded = event.source.isAdded,
name = event.source.name,
index = event.source.index
)
updateUseCase(modelToUpdate).launchIn(viewModelScope)
}
is ItemEvent.QueryChangeEvent -> {
_uiState.value = _uiState.value.copy(
searchQuery = event.newQuery
)
}
is ItemEvent.SortEvent -> {
val curSortType = _uiState.value.sortType
_uiState.value = _uiState.value.copy(
sortType = if(curSortType == SortType.AS_IT_IS)
SortType.ALPHA_NUMERIC
else
SortType.AS_IT_IS
)
}
}
}
private fun getItems(id: String) {
getItemsJob?.cancel()
getItemsJob = getItemsUseCase(id)
.combine(
getItemsFromRoomUseCase()
){ itemsApiResult, roomData ->
when (itemsApiResult) {
is Resource.Success -> {
val data = itemsApiResult.data.toMutableList()
// Look the api result, if the item is added on room, make it added, else make it not added. This ensures API call is done once and every state change happens because of room.
for(i in data.indices) {
val source = data[i]
val itemInRoomData = roomData.find { it.id == source.id }
data[i] = data[i].copy(
isAdded = itemInRoomData != null
)
}
_uiState.value = _uiState.value.copy(
data = data,
isLoading = false,
error = "",
)
}
is Resource.Error -> {
_uiState.value = UIState(
data = emptyList(),
isLoading = false,
error = itemsApiResult.message,
)
}
is Resource.Loading -> {
_uiState.value = UIState(
data = emptyList(),
isLoading = true,
error = "",
)
}
}
}.launchIn(viewModelScope)
}
}
This it the composable:
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun AddableItemsScreen(
itemsViewModel: AddableItemScreenViewModel = hiltViewModel()
) {
val state = itemsViewModel.uiState.value
val controller = LocalNavigationManager.current
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
val mainScrollState = rememberLazyListState()
val focusRequester = remember { FocusRequester() }
// Screen UI
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.BackgroundColor)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
focusManager.clearFocus()
},
) {
LazyColumn(
modifier = Modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
state = mainScrollState,
) {
item {
WhiteSpacer(
whiteSpacePx = 200,
direction = SpacerDirections.VERTICAL
)
}
if (state.isLoading) {
item {
ProgressIndicator()
}
}
if (state.error.isNotEmpty() && state.error.isNotBlank()) {
item {
ErrorText()
}
}
if (state.data.isNotEmpty()) {
val data = if (state.sortType == SortType.ALPHA_NUMERIC)
state.data.sortedBy { it.name }
else
state.data
data.forEach { source ->
if((state.searchQuery.isEmpty() && state.searchQuery.isBlank()) ||
(source.name != null && source.name.contains(state.searchQuery, ignoreCase = true))) {
item {
AddableItem(
modifier = Modifier
.padding(
vertical = dimManager.heightPxToDp(20)
),
text = source.name ?: "",
isAdded = source.isAdded ?: false,
onItemPressed = {
controller.navigate(
Screens.ItemPreviewScreen.route +
"?title=${source.name}" +
"&id=${source.categoryId}" +
"&isAdded=${source.isAdded}"
)
},
onAddPressed = {
itemsViewModel.onEvent(ItemEvent.UpdateEvent(source))
}
)
}
}
}
}
}
Column(
modifier = Modifier
.align(Alignment.TopStart)
.background(
MaterialTheme.colors.BackgroundColor
),
) {
ItemsScreenAppBar(
title = itemsViewModel.title.value,
onSortPressed = {
itemsViewModel.onEvent(ItemEvent.SortEvent)
}
) {
controller.popBackStack()
}
SearchBar(
query = state.searchQuery,
focusRequester = focusRequester,
placeholder = itemsViewModel.title.value,
onDeletePressed = {
itemsViewModel.onEvent(ItemEvent.QueryChangeEvent(""))
},
onValueChanged = {
itemsViewModel.onEvent(ItemEvent.QueryChangeEvent(it))
},
onSearch = {
keyboardController!!.hide()
}
)
WhiteSpacer(
whiteSpacePx = 4,
direction = SpacerDirections.VERTICAL
)
}
}
}
And finally this is the UIState:
data class UIState(
val data: List<ItemModel> = emptyList(),
val isLoading: Boolean = false,
val error: String = "",
val searchQuery: String = "",
val sortType: SortType = SortType.AS_IT_IS,
)
#Parcelize
data class ItemModel (
val id: Int? = null,
var isAdded: Boolean? = null,
val name: String? = null,
val index: Int? = null,
#SerializedName("someSerializedNameForApi")
var id: Int? = null
): Parcelable
Finally, I have a similar issue with almost the same viewModel with the same UI structure. The UI contains an Add All button and when everything is added, it turns to Remove All. I also hold the state of the button in UIState for that screen. When I try to add all items or remove all items, the UI recomposes. But when I try to add or remove a single item, the recomposition does not happen as same as the published code above. Additionally, when I remove one item when everything is added on that screen, the state of the button does change but stops to react when I try to add more. I can also share that code if you people want. I still do not understand why the UI recomposes when I try to sort or try to add-remove all on both screens but does not recompose when the data changes, even though I change the pointer address of the list.
Thanks for any help.
I could not believe that the answer can be so simple but here are the solutions:
For the posted screen, I just changed _uiState.value = _uiState.value.copy(...) to _uiState.value = UIState(...copy and replace everything with old value...) as
_uiState.value = UIState(
data = data,
isLoading = false,
error = "",
searchQuery = _uiState.value.searchQuery,
sortType = _uiState.value.sortType
)
For the second screen, I was just double changing the isAdded value by sending the data directly without copying. As the api call changes the isAdded value again, and the read from room flow changes it again, the state were changed twice.
However, I still wonder why compose didn't recompose when I changed the memory location of data in UIState.

How do I get my DatePicker Dialog to return a date and display it? Issue updating viewmodel

A quick preface, I'm probably overlooking something rather simple but I'm willing to admit I'm not 100% sure of what I'm doing! The project I'm working on is a just for fun project to tinker around with Kotlin and Jetpack Compose by making an Android application.
So to my problem, I'm trying to use a DatePicker dialog using the vanpra compose-material-dialogs. I can get the dialog datepicker to appear but I can't get my selected date to return to my textfield I made after selecting the date from the dialog. It seems I'm running in to some issues updating the viewmodel correctly. Any help at all is greatly appreciated! Thank you!
Below is my DatePicker composable:
#Composable
fun TradeDatePicker(
value: LocalDate,
onValueChanged: (LocalDate) -> Unit,
modifier:Modifier = Modifier,
) {
val dialogState = rememberMaterialDialogState()
MaterialDialog(
dialogState = dialogState,
buttons = {
positiveButton("OK")
negativeButton("CANCEL")
},
backgroundColor = MaterialTheme.colors.background
) {
this.datepicker(onDateChange = onValueChanged)
}
Box(
modifier = modifier
.border(
width = 1.dp,
color = MaterialTheme.colors.onPrimary,
shape = RoundedCornerShape(50),
)
.clip(ButtonShape) //clip keeps animation within box borders
.clickable {
dialogState.show()
}
){
Row (
modifier = Modifier
.padding(16.dp),
){
Text(
text = "Trade Date",
modifier = Modifier
.weight(1F),
)
Icon(
Icons.Default.DateRange,
contentDescription = "Select Date",
)
}
}
}
Here is my screen where you select the datepicker field to display the datepicker dialog, it seems I'm trying to pass a LocalDate to the viewmodel but it requires a long(that's how I thought the date got returned from the dialog, as a long):
#Composable
fun AddEditTradeScreen(
navController: NavController,
viewModel: AddEditTradeViewModel = hiltViewModel()
){
val tradesymbolState = viewModel.tradeSymbol.value
val tradequantState = viewModel.tradeQuantity.value
val tradeDateState = viewModel.tradeDate
val scaffoldState = rememberScaffoldState()
val value: LocalDate
LaunchedEffect(key1 = true){
viewModel.eventFlow.collectLatest { event ->
when(event){
is AddEditTradeViewModel.UiEvent.ShowSnackbar ->{
scaffoldState.snackbarHostState.showSnackbar(
message = event.message
)
}
is AddEditTradeViewModel.UiEvent.SaveTrade -> {
navController.navigateUp()
}
}
}
}
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = {
viewModel.onEvent(AddEditTradeEvent.SaveTrade)
},
backgroundColor = MaterialTheme.colors.primary
) {
Icon(imageVector = Icons.Default.Save, contentDescription = "Save Trade")
}
},
scaffoldState = scaffoldState
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Enter Your Tradeeee",
style = MaterialTheme.typography.h4,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
TransparentHintTextField(
text = tradesymbolState.text,
hint = tradesymbolState.hint,
onValueChange = {
viewModel.onEvent(AddEditTradeEvent.EnteredSymbol(it))
},
onFocusChange = {
viewModel.onEvent(AddEditTradeEvent.ChangeSymbolFocus(it))
},
isHintVisible = tradesymbolState.isHintVisible,
singleLine = true,
textStyle = MaterialTheme.typography.h5
)
Spacer(modifier = Modifier.height(16.dp))
TransparentHintTextField(
text = tradequantState.text,
hint = tradequantState.hint,
onValueChange = {
viewModel.onEvent(AddEditTradeEvent.EnteredQuantity(it))
},
onFocusChange = {
viewModel.onEvent(AddEditTradeEvent.ChangeQuantityFocus(it))
},
isHintVisible = tradequantState.isHintVisible,
textStyle = MaterialTheme.typography.body1,
//modifier = Modifier.fillMaxHeight()
)
Spacer(modifier = Modifier.height(16.dp))
TradeDatePicker(
value = LocalDate.now(),
onValueChanged = {
viewModel.onEvent(AddEditTradeEvent.EnteredTradeDate(it))
},
modifier = Modifier
.fillMaxWidth()
)
}
}
}
This is my viewmodel that I'm trying to update:
#HiltViewModel
class AddEditTradeViewModel #Inject constructor(
private val tradeUseCases: TradeUseCases,
savedStateHandle: SavedStateHandle
) : ViewModel(){
private val _tradeSymbol = mutableStateOf(
TradeTextFieldState(
hint = "Enter Trade Symobol"
))
val tradeSymbol: State<TradeTextFieldState> = _tradeSymbol
private val _tradeQuantity = mutableStateOf(
TradeTextFieldState(
hint = "Enter Number of Shares"
))
val tradeQuantity: State<TradeTextFieldState> = _tradeQuantity
private var _tradeDate : String? by remember {
mutableStateOf(null)
}
val tradeDate = { date: Long? ->
_tradeDate = date?.toString()?:""
}
private val _eventFlow = MutableSharedFlow<UiEvent>()
val eventFlow = _eventFlow.asSharedFlow()
private var currentTradeId: Int? = null
init{
savedStateHandle.get<Int>("tradeId")?.let { tradeId ->
if (tradeId != -1){
viewModelScope.launch {
tradeUseCases.getTrade(tradeId)?.also { trade ->
currentTradeId = trade.id
_tradeSymbol.value = tradeSymbol.value.copy(
text = trade.tradeSymb,
isHintVisible = false
)
_tradeQuantity.value = tradeQuantity.value.copy(
text = trade.tradequant,
isHintVisible = false
)
}
}
}
}
}
fun onEvent(event: AddEditTradeEvent){
when(event){
is AddEditTradeEvent.EnteredSymbol -> {
_tradeSymbol.value = tradeSymbol.value.copy(
text = event.value
)
}
is AddEditTradeEvent.ChangeSymbolFocus -> {
_tradeSymbol.value = tradeSymbol.value.copy(
isHintVisible = !event.focusState.isFocused &&
tradeSymbol.value.text.isBlank()
)
}
is AddEditTradeEvent.EnteredQuantity -> {
_tradeQuantity.value = tradeQuantity.value.copy(
text = event.value
)
}
is AddEditTradeEvent.ChangeQuantityFocus -> {
_tradeQuantity.value = tradeQuantity.value.copy(
isHintVisible = !event.focusState.isFocused &&
_tradeQuantity.value.text.isBlank()
)
}
is AddEditTradeEvent.EnteredTradeDate -> {
_tradeDate.value = tradeDate.value.copy(
text = event.value
)
}
is AddEditTradeEvent.SaveTrade -> {
viewModelScope.launch{
try {
tradeUseCases.addTrade(
Trade(
tradeSymb = tradeSymbol.value.text,
tradequant= tradeQuantity.value.text,
timestamp = System.currentTimeMillis(),
//tradestatus = "Long",
//color = MaterialTheme.colors.primary.green
id = currentTradeId
)
)
_eventFlow.emit(UiEvent.SaveTrade)
} catch (e: InvalidTradeException){
_eventFlow.emit(
UiEvent.ShowSnackbar(
message = e.message ?: "Couldn't save trade"
)
)
}
}
}
}
}
}
Lastly this is the event sealed class, not sure I'm using this correctly but in here for the "EnteredTradeDate" I declare it as a long and I'm not sure this is correct since I'm having trouble passing it from the screen to the viewmodel.
sealed class AddEditTradeEvent {
data class EnteredSymbol(val value: String): AddEditTradeEvent()
data class ChangeSymbolFocus(val focusState: FocusState): AddEditTradeEvent()
data class EnteredQuantity(val value: String): AddEditTradeEvent()
data class ChangeQuantityFocus(val focusState: FocusState): AddEditTradeEvent()
data class EnteredTradeDate(val value: Long) : AddEditTradeEvent()
data class ChangeDateFocus(val focusState: FocusState): AddEditTradeEvent()
object SaveTrade: AddEditTradeEvent()
}

Categories

Resources