I am trying to set onClick() in IconButton like this
IconButton(onClick = {
navController?.navigate(NavigationScreen.Search.route)
}) {
Icon(
painter = painterResource(id = R.drawable.hamburger_setting),
"UserTweetSetting",
modifier = Modifier
.size(12.dp)
.align(Alignment.CenterVertically),
tint = Color.DarkGray
)
}
}
I call the TweetContentCardRecyclerView() funtion in Home(). For that reason, onClick() where i set in IconButton is not working. Can i set onClick() in another way in TweetContentCard() so that it can work or I have to set onClick() in TweetContentCardRecyclerView() ?
sorry for my poor english
#Composable
fun Home(navController: NavHostController? = null) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White),
) {
TweetContentCardRecyclerView()
}
}
#Composable
fun TweetContentCard(
navController: NavHostController? = null,
userProfilePicture: Int = 0,
userFullName: String = "",
userName: String = "",
tweetTime: String = "",
tweetTextContent: String = "",
tweetImageContent: Int = 0,
totalComment: String = "",
totalReTweet: String = "",
totalLike: String = ""
) {
Spacer(modifier = Modifier.height(10.dp))
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp)
.widthIn(min = 20.dp, max = 500.dp)
.background(MaterialTheme.colors.background)
) {
Row(modifier = Modifier.padding(top = 5.dp)) {
// User Profile Picture
SetImage(
image = userProfilePicture,
height = 50,
width = 50,
roundCorner = 50
)
Spacer(modifier = Modifier.width(10.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
// User Full Name
SetText(text = "$userFullName ", weight = FontWeight.Medium)
SetText(
text = "#",
weight = FontWeight.Light,
size = 11,
color = Color.DarkGray,
bottomPadding = 2
)
// UserName
SetText(
text = userName,
weight = FontWeight.Light,
size = 13,
color = Color.DarkGray,
bottomPadding = 2
)
SetText(
text = " * ",
weight = FontWeight.ExtraLight,
size = 10
)
// Tweet Post Time
SetText(
text = tweetTime,
weight = FontWeight.Light,
size = 11,
color = Color.DarkGray,
bottomPadding = 2
)
Spacer(modifier = Modifier.width(40.dp))
**If user click this, he should to the Search Screen**
IconButton(onClick = {
navController?.navigate(NavigationScreen.Search.route)
}) {
Icon(
painter = painterResource(id = R.drawable.hamburger_setting),
"UserTweetSetting",
modifier = Modifier
.size(12.dp)
.align(Alignment.CenterVertically),
tint = Color.DarkGray
)
}
}
}
Column(modifier = Modifier.padding(top = 5.dp)) {
if (tweetTextContent.isNotEmpty() and (tweetImageContent != 0)) {
TweetText(tweetTextContent)
TweetImage(tweetImageContent)
} else if (tweetTextContent.isNotEmpty() and (tweetImageContent == 0))
TweetText(tweetTextContent)
else
TweetImage(tweetImageContent)
Row(modifier = Modifier.padding(top = 5.dp)) {
TweetReactionItem(
icon = R.drawable.tweet_comment,
contentDesc = "Comment",
text = totalComment
)
TweetReactionItem(
icon = R.drawable.retweet,
contentDesc = "Retweet",
text = totalReTweet
)
TweetReactionItem(
icon = R.drawable.tweet_like,
contentDesc = "Like",
text = totalLike
)
}
Spacer(modifier = Modifier.height(10.dp))
Divider(color = Color.Gray, thickness = 0.2.dp)
}
}
}
#Composable
fun TweetContentCardRecyclerView() {
val tweetContent = remember { DataProvider.tweetContent }
LazyColumn {
items(tweetContent) { content ->
TweetContentCard(
userProfilePicture = content.userProfilePicture,
userFullName = content.userFullName,
userName = content.userName,
tweetTime = content.tweetTime,
tweetTextContent = content.tweetTextContent,
tweetImageContent = content.tweetImageContent,
totalComment = content.totalComment,
totalReTweet = content.totalReTweet,
totalLike = content.totalLike
)
}
}
}
you've defined navController arg optional and you don't pass it to TweetContentCard(). As a result it's null and because of your null check before navigate (navController?.) it doesn't be executed at all.
fun TweetContentCard(
navController: NavHostController? = null,// OPTIONAL
It can be solved by passing navController:
fun TweetContentCardRecyclerView(navController:NavHostController) {
LazyColumn {
items(tweetContent) { content ->
TweetContentCard(
navController = navController,
userProfilePicture = content.userProfilePicture,
userFullName = content.userFullName,
....
But it's good practice to pass the onClick as lambda to your item. No need to pass navController
#Composable
fun TweetContentCard(
modifier: Modifier = Modifier,
onClick: () -> Unit,
...
) {
...
IconButton(onClick = { onClick() }) {
...
Related
I have basic Login screen in my app, which looks like this:
LoginScreen.kt:
#Composable
fun LoginScreen(navController: NavController, viewModel: LoginViewModel) {
val userEmail = viewModel.userEmail.collectAsState()
val userPassword = viewModel.userPassword.collectAsState()
val isLoginPending = viewModel.isLoginPending.collectAsState()
val isLoginButtonEnabled = viewModel.isLoginButtonEnabled.collectAsState()
val scaffoldState = rememberScaffoldState()
if (viewModel.getOnboardingStatus() == OnboardingStatus.NOT_COMPLETED) {
navController.navigate(Screen.Onboarding.route)
}
LaunchedEffect(key1 = true) {
viewModel.loginError.collectLatest {
it?.let {
scaffoldState.snackbarHostState.showSnackbar(message = it)
}
}
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.recomposeHighlighter(),
scaffoldState = scaffoldState
) {
Box(modifier = Modifier
.fillMaxSize()
.recomposeHighlighter()) {
if (isLoginPending.value) {
ProgressBar(
modifier = Modifier
.align(Alignment.Center)
.recomposeHighlighter(),
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center)
.recomposeHighlighter(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Login",
fontWeight = FontWeight.Bold,
fontSize = 36.sp
)
Spacer(modifier = Modifier
.height(64.dp)
.recomposeHighlighter())
UserInputTextField(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
.recomposeHighlighter(),
label = "Email",
inputState = userEmail.value,
onValueChange = { viewModel.onEvent(LoginEvent.EnteredEmail(it)) },
isLoginPending = isLoginPending.value
)
Spacer(modifier = Modifier
.height(8.dp)
.recomposeHighlighter())
UserInputTextField(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
.recomposeHighlighter(),
label = "Password",
inputState = userPassword.value,
onValueChange = { viewModel.onEvent(LoginEvent.EnteredPassword(it)) },
isLoginPending = isLoginPending.value,
keyboard = KeyboardOptions(keyboardType = KeyboardType.Password),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier
.height(32.dp)
.recomposeHighlighter())
Button(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
end = 16.dp
)
.recomposeHighlighter(),
enabled = isLoginButtonEnabled.value,
onClick = {
viewModel.onEvent(LoginEvent.Login)
}
) {
Text(text = "Login")
}
}
ClickableButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 32.dp)
.recomposeHighlighter(),
text = "No account? Click to register",
onTextClicked = { navController.navigate(Screen.Register.route) }
)
}
}
}
#Composable
private fun UserInputTextField(
modifier: Modifier,
label: String,
inputState: String,
onValueChange: (String) -> Unit,
isLoginPending: Boolean,
keyboard: KeyboardOptions? = null,
visualTransformation: VisualTransformation? = null
) {
OutlinedTextField(
value = inputState,
onValueChange = { onValueChange(it) },
label = { Text(text = label) },
placeholder = { Text(text = label) },
singleLine = true,
enabled = !isLoginPending,
modifier = modifier,
keyboardOptions = keyboard?.let { keyboard } ?: KeyboardOptions.Default,
visualTransformation = visualTransformation?.let { visualTransformation } ?: VisualTransformation.None
)
}
And I am using it with view model to decide if login button should be enabled, to login user etc.
LoginViewModel:
#HiltViewModel
class LoginViewModel #Inject constructor(private val repository: LoginRepository): ViewModel() {
private val _userEmail = MutableStateFlow<String>(value = String())
val userEmail: StateFlow<String> = _userEmail.asStateFlow()
private val _userPassword = MutableStateFlow<String>(value = String())
val userPassword: StateFlow<String> = _userPassword.asStateFlow()
private val _isLoginPending = MutableStateFlow<Boolean>(value = false)
val isLoginPending: StateFlow<Boolean> = _isLoginPending.asStateFlow()
private val _loginError = MutableSharedFlow<String?>()
val loginError: SharedFlow<String?> = _loginError.asSharedFlow()
private val _isLoginButtonEnabled = MutableStateFlow<Boolean>(value = false)
val isLoginButtonEnabled: StateFlow<Boolean> = _isLoginButtonEnabled.asStateFlow()
fun getOnboardingStatus(): OnboardingStatus {
return repository.isOnboardingCompleted()
}
fun onEvent(event: LoginEvent) {
when (event) {
is LoginEvent.EnteredEmail -> {
_userEmail.value = event.email
_isLoginButtonEnabled.value = _userEmail.value.isNotEmpty() && _userPassword.value.isNotEmpty()
}
is LoginEvent.EnteredPassword -> {
_userPassword.value = event.password
_isLoginButtonEnabled.value = _userEmail.value.isNotEmpty() && _userPassword.value.isNotEmpty()
}
is LoginEvent.Login -> {
viewModelScope.launch {
_isLoginPending.value = true
_isLoginButtonEnabled.value = false
val result = repository.loginUser(_userEmail.value, _userPassword.value)
when (result) {
is AuthRequestState.Success -> { /* nothing to do */ }
is AuthRequestState.Fail -> {
_loginError.emit(result.msg)
}
}
_isLoginPending.value = false
_isLoginButtonEnabled.value = true
}
}
}
}
}
However the problem is that when I am typing something in TextFields in layout inspector I see that other elements of UI (like Button and ClickableButton) are recomposed:
How to avoid such behavior?
I'm using room database with jetpack compose,upon deleting all items the ui is not recomposing unless i move to another screen and come back again , any help would be appreciated , Thank you
This is my code
fun CheckOutScreen(shopViewModel: ShopViewModel,
navHostController: NavHostController,
list : SnapshotStateList<PurchaseModel> ) {
val firebaseAuth = FirebaseAuth.getInstance()
val databaseReference = FirebaseDatabase.getInstance().reference
val context = LocalContext.current
val totalAmount = remember { mutableStateOf(0.0) }
val count = remember { mutableStateOf(0) }
val isDialogShowing = remember { mutableStateOf(false) }
val isProgressShowing = remember { mutableStateOf(false) }
val isDataSaving = remember { mutableStateOf(false) }
val isMovingToAnotherScreen = remember { mutableStateOf(false)}
list.forEach {
if(count.value < list.size){
totalAmount.value += it.totalPrice
count.value++
}
}
Scaffold{
Column {
Row(modifier = Modifier.fillMaxWidth()) {
Text(text = "Cart", modifier = Modifier
.weight(1f)
.padding(10.dp), color = Color.DarkGray,
style = MaterialTheme.typography.h1, fontSize = 17.sp)
IconButton(onClick = {
isDialogShowing.value = true
}) {
Icon(Icons.Filled.Delete,"")
}
}
if(list.size == 0){
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Text("No Items In The Cart", style = MaterialTheme.typography.h1,
color = Color.DarkGray , fontSize = 17.sp)
}
}
else {
Box(contentAlignment = Alignment.Center){
Column() {
Text(text = "Purchase To : " + list[0].fullName, modifier = Modifier.padding(start = 10.dp,
end = 10.dp, top = 5.dp), fontSize = 17.sp,style = MaterialTheme.typography.body1)
LazyColumn(modifier = Modifier
.weight(1f)
.padding(top = 10.dp)){
items(list.size, key = { item -> item.hashCode()}){ pos ->
val dismissState = rememberDismissState(confirmStateChange = {
if(it == DismissValue.DismissedToStart || it == DismissValue.DismissedToEnd){
shopViewModel.deletePurchase(list[pos].purchaseId!!)
Toast.makeText(context,"Item Successfully Dismissed",Toast.LENGTH_SHORT).show()
}
true
})
if(!isMovingToAnotherScreen.value){
SwipeToDismiss(
state = dismissState,
background = {},
dismissContent = {
Card(elevation = 10.dp, modifier = Modifier
.clip(shape = RoundedCornerShape(6.dp))
.padding(5.dp)) {
Row(modifier = Modifier.fillMaxWidth()) {
Box(contentAlignment = Alignment.Center) {
AsyncImage(model = ImageRequest.Builder(context).data(list[pos].productImage).build(),
contentScale = ContentScale.Crop,
modifier = Modifier
.width(80.dp)
.height(80.dp)
.padding(10.dp),
contentDescription = "")
}
Column(modifier = Modifier.weight(1f)) {
Text(list[pos].title, modifier = Modifier.padding(5.dp), fontWeight = FontWeight.Bold)
Text("Quantity : " + list[pos].totalQuantity)
Text("Due To : " + list[pos].totalPrice)
}
}
}
},
directions = setOf(DismissDirection.StartToEnd,DismissDirection.EndToStart))
}
}
}
Card(modifier = Modifier
.fillMaxWidth()
.clip(shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))) {
Button(modifier = Modifier.padding(start = 20.dp, end = 20.dp),onClick = {
isProgressShowing.value = true
}) {
Text("Pay ${totalAmount.value}")
}
}
}
}
AnimatedVisibility(visible = isProgressShowing.value) {
CircularProgressIndicator(color = Color.DarkGray)
}
}
}
if(isProgressShowing.value){
val map = hashMapOf<String,Any>()
map["list"] = list
map["uid"] = firebaseAuth.currentUser!!.uid
map["totalPrice"] = totalAmount.value.toString()
val db = databaseReference.child("Purchases")
db.child(firebaseAuth.currentUser!!.uid)
.child(Calendar.getInstance().timeInMillis.toString())
.setValue(map)
.addOnSuccessListener {
isDataSaving.value = true
}
.addOnFailureListener {
}
}
if(isDialogShowing.value){
AlertDialog(
onDismissRequest = { isDialogShowing.value = false },
confirmButton = {
TextButton(onClick = {
shopViewModel.deletePurchases()
isDialogShowing.value = false
Toast.makeText(context,"All items are successfully removed",Toast.LENGTH_SHORT).show()
}) {
Text("Proceed")
}
},
dismissButton = {
TextButton(onClick = { isDialogShowing.value = false }) {
Text("Cancel")
}
},
title = { Text("Removing Items ") },
text = { Text("Do you want to remove items from cart ! ") }
)
}
if(isDataSaving.value){
LaunchedEffect(Unit){
delay(3000)
isMovingToAnotherScreen.value = true
shopViewModel.deletePurchases()
isProgressShowing.value = false
isDataSaving.value = false
navHostController.navigate(AppRouting.Payment.route)
}
}
}
BackHandler {
isMovingToAnotherScreen.value = true
navHostController.popBackStack()
}
}
It's most likely because of not updating value of your State. In you example it's always the same list. You either need to set value with new instance of list or use SnapshotStateList. You can check this answer also.
I'm trying to do a ConstraintLayout in jetpack compose as I am having problems doing too many nested Columns and Rows.
Here is what I have:
#Composable
fun StateAndZipLayout(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
stateError: Boolean,
zipError: Boolean
) {
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val componentWidth = (screenWidth - 48.dp)/2
ConstraintLayout(modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()) {
val rightGuideline = createGuidelineFromStart(0.5f)
val (stateDropDown, shippingField) = createRefs()
StateSelection(
modifier = modifier
.constrainAs(stateDropDown) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(rightGuideline, margin = 8.dp)
}
.requiredWidth(componentWidth)
.wrapContentHeight(),
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
label = "State",
error = stateError,
)
ShippingField(
modifier = modifier
.constrainAs(shippingField) {
start.linkTo(rightGuideline, margin = 8.dp)
top.linkTo(stateDropDown.top)
bottom.linkTo(stateDropDown.bottom)
end.linkTo(parent.end)
}
.requiredWidth(componentWidth),
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_ZIP,
label = "Zip",
valueField = selectedLocation.zipCode,
error = zipError
)
}
}
Here is my state selection view:
#Composable
fun StateSelection(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
error: Boolean,
label: String
) {
// State variables
val statesMap = AddressUtils.mapOfAmericanStatesToValue
var stateName: String by remember { mutableStateOf(selectedLocation.shippingState) }
var expanded by remember { mutableStateOf(false) }
val focusManager = LocalFocusManager.current
var errorState by remember { mutableStateOf(error) }
Column {
Row(
Modifier
.clickable {
expanded = !expanded
},
) { // Anchor view
TextField(
modifier = Modifier
.fillMaxWidth(),
value = stateName,
onValueChange = {
onFormChanged(FormType.SHIPPING_COUNTRY, it)
},
label = { Text(text = label) },
textStyle = MaterialTheme.typography.subtitle1,
singleLine = true,
trailingIcon = {
IconButton(onClick = { expanded = true }) {
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = "",
tint = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.onPrimary
)
}
},
keyboardActions = KeyboardActions(onNext = {
focusManager.moveFocus(
FocusDirection.Down
)
}),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Text
),
colors = TextFieldDefaults.textFieldColors(
cursorColor = MaterialTheme.colors.secondary,
textColor = MaterialTheme.colors.onPrimary,
focusedLabelColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
focusedIndicatorColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
backgroundColor = MaterialTheme.colors.secondaryVariant
)
) // state name label
DropdownMenu(expanded = expanded, onDismissRequest = {
expanded = false
}) {
statesMap.asIterable().iterator().forEach {
val (key, value) = it
DropdownMenuItem(
onClick = {
expanded = false
stateName = key
onFormChanged(FormType.SHIPPING_STATE, key)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = key)
}
}
}
}
if (errorState && error) {
ErrorMessages(modifier = modifier, message = "$label is required")
}
}
}
This is what it looks like, the state drop down and the zip code field are overlapping:
I'm trying to do a constraint layout in my Compose view. Unfortunately, I get no output. I was having problems using columns to show error output after the TextField, so in desperation, I am using a constraint layout. Here is the component:
#Composable
fun StateAndZip(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
stateError: Boolean,
zipError: Boolean
) {
ConstraintLayout {
val (stateDropDown, shippingField) = createRefs()
StateSelection(
modifier = modifier.constrainAs(stateDropDown) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(shippingField.start, margin = 8.dp)
width = Dimension.preferredWrapContent
height = Dimension.fillToConstraints
},
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
label = "State",
error = stateError,
)
ShippingField(
modifier = modifier.constrainAs(shippingField) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(parent.end)
width = Dimension.preferredWrapContent
height = Dimension.fillToConstraints
},
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_ZIP,
label = "Zip",
valueField = selectedLocation.zipCode,
error = zipError
)
}
}
Here is where I'm calling it:
#Composable
fun ShippingForm(
modifier: Modifier = Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
validateErrors: Boolean
) {
var cityError by remember { mutableStateOf(false)}
var stateError by remember { mutableStateOf(false)}
var countryError by remember {mutableStateOf(false)}
var zipError by remember { mutableStateOf(false)}
if (validateErrors) {
if (selectedLocation.shippingCity.isBlank()) {
cityError = true
}
if (selectedLocation.shippingState.isBlank()) {
stateError = true
}
if (selectedLocation.zipCode.isBlank()) {
zipError = true
}
if (selectedLocation.shippingCountry.isBlank()) {
countryError = true
}
}
//already in a column, so no need to add another one.
Spacer(modifier = Modifier.height(spacerHeight()))
ShippingField(
modifier = modifier,
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_2,
label = "Apartment, suite, etc. (Optional)",
valueField = selectedLocation.shipping2,
error = false
)
Spacer(modifier = Modifier.height(spacerHeight()))
ShippingField(
modifier = modifier,
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_CITY,
label = "City",
valueField = selectedLocation.shippingCity,
error = cityError
)
Spacer(modifier = Modifier.height(spacerHeight()))
StateAndZip(
modifier = modifier,
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
stateError = stateError,
zipError = zipError
)
Spacer(modifier = Modifier.height(spacerHeight()))
CountrySelection(
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
label = "Country",
error = countryError
)
}
The individual views are quite complex. Here is the stateDropDown:
#Composable
fun StateSelection(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
error: Boolean,
label: String
) {
// State variables
val statesMap = AddressUtils.mapOfAmericanStatesToValue
var stateName: String by remember { mutableStateOf(selectedLocation.shippingState) }
var expanded by remember { mutableStateOf(false)}
val focusManager = LocalFocusManager.current
var errorState by remember { mutableStateOf(error)}
// Create references for the composables to constrain
Box(
contentAlignment = Alignment.CenterStart,
modifier = modifier
) {
Row(
Modifier
.clickable {
expanded = !expanded
},
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) { // Anchor view
TextField(
modifier = Modifier
.fillMaxWidth(),
value = stateName,
onValueChange = {
onFormChanged(FormType.SHIPPING_COUNTRY, it)
},
label = { Text(text = label) },
textStyle = MaterialTheme.typography.subtitle1,
singleLine = true,
trailingIcon = {
IconButton(onClick = { expanded = true }) {
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = "",
tint = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.onPrimary
)
}
},
keyboardActions = KeyboardActions(onNext = {
focusManager.moveFocus(
FocusDirection.Down
)
}),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Text
),
colors = TextFieldDefaults.textFieldColors(
cursorColor = MaterialTheme.colors.secondary,
textColor = MaterialTheme.colors.onPrimary,
focusedLabelColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
focusedIndicatorColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
backgroundColor = MaterialTheme.colors.secondaryVariant
)
) // state name label
DropdownMenu(expanded = expanded, onDismissRequest = {
expanded = false
}) {
statesMap.asIterable().iterator().forEach {
val (key, value) = it
DropdownMenuItem(
onClick = {
expanded = false
stateName = key
onFormChanged(FormType.SHIPPING_STATE, key)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = key)
}
}
}
}
}
if (errorState && error) {
ErrorMessages(modifier = modifier, message = "$label is required")
}
}
I have made datePicker composeable, when i call that composable in on click of Icon , it show
#Composable invocation can happen only within the context of composable
I simply want to open Date picker while on clicking and on ok press My Text is updated .
My DatePicker
#Composable
fun DatePicker(onDateSelected: (LocalDate) -> Unit, onDismissRequest: () -> Unit) {
val selDate = remember { mutableStateOf(LocalDate.now()) }
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = MaterialTheme.colors.surface,
shape = RoundedCornerShape(size = 16.dp)
)
) {
Column(
Modifier
.defaultMinSize(minHeight = 72.dp)
.fillMaxWidth()
.background(
color = MaterialTheme.colors.primary,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
)
.padding(16.dp)
) {
Text(
text = "Select date".uppercase(),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onPrimary
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = selDate.value.format(DateTimeFormatter.ofPattern("MMM d, YYYY")),
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.onPrimary
)
Spacer(modifier = Modifier.size(16.dp))
}
CustomCalendarView(onDateSelected = {
selDate.value = it
})
Spacer(modifier = Modifier.size(8.dp))
Row(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = 16.dp, end = 16.dp)
) {
TextButton(
onClick = onDismissRequest
) {
//TODO - hardcode string
Text(
text = "Cancel",
style = MaterialTheme.typography.button,
color = MaterialTheme.colors.onPrimary
)
}
TextButton(
onClick = {
onDateSelected(selDate.value)
onDismissRequest()
}
) {
//TODO - hardcode string
Text(
text = "OK",
style = MaterialTheme.typography.button,
color = MaterialTheme.colors.onPrimary
)
}
}
}
}
}
#Composable
fun CustomCalendarView(onDateSelected: (LocalDate) -> Unit) {
AndroidView(
modifier = Modifier.wrapContentSize(),
factory = { context ->
CalendarView(ContextThemeWrapper(context, R.style.CalenderViewCustom))
},
update = { view ->
view.setOnDateChangeListener { _, year, month, dayOfMonth ->
onDateSelected(
LocalDate
.now()
.withMonth(month + 1)
.withYear(year)
.withDayOfMonth(dayOfMonth)
)
}
}
)
}
ViewModel Class :
#HiltViewModel
class AddWeightViewModel #Inject() constructor(private val repository: WeightRepository)
:
ViewModel() {
val weightState = mutableStateOf("")
val dateState = mutableStateOf("")
fun onWeightChange(weight: String) {
weightState.value = weight
}
fun onDateChange(date : String) {
dateState.value = date
}
fun addWeight() = viewModelScope.launch {
val weight = Weight( 0, weightState.value , dateState.value )
repository.addWeight(weight)
}
}
MyWeightScreen :
#ExperimentalComposeUiApi
#Composable
fun AddWeightScreen(navController: NavController, viewModel: AddWeightViewModel) {
Column(modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colors.primary)
.padding(bottom = 56.dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Cancel", fontSize = 16.sp, modifier = Modifier
.padding(16.dp)
.align(alignment = Alignment.Start)
.clickable {
navController.navigate(Screens.MyWeight.route)
}, color = background)
WeightData(viewModel)
Button(
onClick = {
viewModel.addWeight()
navController.navigate(Screens.MyWeight.route)
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = background,
contentColor = textDark)
) {
Text(text = "Done", fontSize = 18.sp)
}
}
}
#ExperimentalComposeUiApi
#Composable
fun WeightData(viewModel: AddWeightViewModel) {
val selDate = LocalDate.now()
val date = viewModel.dateState.value
val keyboardController = LocalSoftwareKeyboardController.current
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(22.dp),
modifier = Modifier
.drawBehind {
drawCircle(
color = textColor,
radius = 450f,
style = Stroke(width = 1.dp.toPx())
)
}
) {
Text(text = "Enter Weight", color = Color.White.copy(alpha = .8f))
TextField(
value = viewModel.weightState.value,
onValueChange = { viewModel.onWeightChange(it) },
modifier = Modifier.background(color = MaterialTheme.colors.primary),
keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number,imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
singleLine = true)
Row(verticalAlignment = Alignment.CenterVertically , horizontalArrangement = Arrangement.Center ) {
Icon(imageVector = Icons.Default.DateRange,
contentDescription = null,
modifier = Modifier.clickable { DatePicker(onDateSelected = ) {
}})
Spacer(modifier = Modifier.width(20.dp))
DatePicker(onDateSelected = {selDate}) {
viewModel.onDateChange(date)
}
}
}
This is what i did .