Avoid recompositions of other elements when changing TextField value - android

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?

Related

Jetpack compose firebase auth, why is ui updating only after reloging?

In my application I'm using FirebaseAuth as the logging system. In HomeScreen I'm displaying currently reading books based on User's firestore database. The user can update this list by adding the book to firestore database in DetailsScreen. The problem is that even though the database is being updated, it is being shown on the UI only if the user sign out and then log in again, it is not being shown while the user is logged in. How can i fix this? I'm providing necessarry code down below. I know, that's a lot but I simply can't find the issue.
HomeScreen
#Composable
fun HomeScreen(
navController: NavController,
viewModel: HomeViewModel = hiltViewModel(),
commonViewModel: CommonViewModel
) {
val currentUser = FirebaseAuth.getInstance().currentUser
val context = LocalContext.current
val userBooks = viewModel.booksFromFB.value.filter {
it.userId == currentUser?.uid.toString()
}
Log.d("Test", "${userBooks}")
if (viewModel.isLoading.value) {
CircularProgressIndicator()
} else {
Box(
modifier = Modifier
.fillMaxSize()
.background(gradient(colors = listOf(AppColors.mBackground, AppColors.mBackgroundSec)))
) {
Log.d("Test", currentUser?.uid.toString())
Column(
modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 16.dp, bottom = 16.dp)
) {
Header(nick = currentUser?.email?.split('#')?.first().toString()) {
navController.navigate(Screen.Profile.route)
}
Spacer(modifier = Modifier.height(20.dp))
CurrentlyReadingSection(
context = context,
navController = navController,
userBooks = userBooks,
commonViewModel = commonViewModel
)
CurrentlyReadingSection
#Composable
fun CurrentlyReadingSection(
userBooks: List<BookFB>,
context: Context = LocalContext.current,
commonViewModel: CommonViewModel,
navController: NavController
) {
Column(
modifier = Modifier.padding()
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Currently reading",
color = AppColors.mTextWhite,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(10.dp))
LazyRow() {
items(userBooks) { book ->
BookRow(
modifier = Modifier.padding(4.dp),
onItemClicked = { }
) {
val isExpanded = remember {
mutableStateOf(false)
}
val isRead = rememberSaveable {
mutableStateOf(false)
}
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.mBackgroundSec),
contentAlignment = BottomCenter
) {
Row {
AnimatedVisibility(visible = isExpanded.value) {
Box(
modifier = Modifier.fillMaxSize()
.clickable { isExpanded.value = !isExpanded.value },
contentAlignment = Center
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MyButton(
modifier = Modifier
.fillMaxWidth(0.7f)
.fillMaxHeight(0.2f)
.clip(RoundedCornerShape(12.dp)),
text = "READ",
fontSize = 12,
contentPadding = 8
) {
isRead.value = !isRead.value
isExpanded.value = !isExpanded.value
}
Spacer(modifier = Modifier.height(4.dp))
MyButton(
modifier = Modifier
.fillMaxWidth(0.7f)
.fillMaxHeight(0.24f)
.clip(RoundedCornerShape(12.dp)),
text = "RATE",
fontSize = 12,
contentPadding = 8
) {
commonViewModel.currentBook.value = book
navController.navigate(Screen.Rate.route)
}
}
}
}
AsyncImage(
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(12.dp))
.clickable { isExpanded.value = !isExpanded.value },
model = ImageRequest.Builder(context)
.data(
if (isValid(book.image))
book.image
else
com.example.read.R.drawable.imagenotfound
)
.crossfade(true)
.build(),
contentScale = ContentScale.FillBounds,
contentDescription = "Book image"
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.1f)
.background(if (isRead.value) AppColors.mGreen else AppColors.mRed)
)
}
}
}
}
}
}
BookRow - for CurrentlyReadingSection
#Composable
fun BookRow(
modifier: Modifier = Modifier,
book: BookFB = BookFB(),
category: MyCategory = MyCategory("", ""),
shapeDp: Int = 12,
heightSize: Int = 170,
widthSize: Int = 120,
isForYou: Boolean = false,
isYourCollection: Boolean = false,
onItemClicked: () -> Unit = {},
content: #Composable() () -> Unit,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = if (isYourCollection) Modifier.widthIn(max = widthSize.dp) else Modifier.fillMaxWidth()
) {
Surface(
shape = RoundedCornerShape(shapeDp.dp),
elevation = 4.dp,
modifier = modifier.border(2.dp, color = Color.Black, shape = RoundedCornerShape(shapeDp.dp))
) {
Box(
modifier = Modifier
.clickable {
onItemClicked()
}
.height(heightSize.dp)
.width(widthSize.dp)
.background(AppColors.mBackgroundSec))
{
content()
}
Adding and getting books from firestore database in repository
class FirebaseRepositoryImpl #Inject constructor(
private val queryBook: CollectionReference
): FirebaseRepository {
override suspend fun addToFirebase(book: BookFB) {
if (book.toString().isNotEmpty()) {
queryBook.add(book)
.addOnSuccessListener { documentRef ->
val documentId = documentRef.id
queryBook.document(documentId)
.update(hashMapOf("id" to documentId) as Map<String, Any>)
.addOnFailureListener {
Log.w("Error", "AddToFirebase: Failed updating doc", it)
}
}
}
}
override suspend fun getBooksFromFB(): Resource<List<BookFB>> {
return try {
Resource.Loading(true)
val response = queryBook.get().await().documents.map { documentSnapshot ->
documentSnapshot.toObject(BookFB::class.java)!!
}
if (response.isNotEmpty())
Resource.Loading(false)
Resource.Success(response)
} catch (exception: FirebaseFirestoreException) {
Resource.Error(exception.message.toString())
}
}
}
Getting the books in ViewModel
#HiltViewModel
class HomeViewModel #Inject constructor(
private val repository: FirebaseRepository
): ViewModel() {
private val _booksFromFB = mutableStateOf(listOf<BookFB>())
val booksFromFB = _booksFromFB
private val _isLoading = mutableStateOf(true)
val isLoading = _isLoading
init {
getBooksFromFB()
}
fun getBooksFromFB() {
viewModelScope.launch {
_isLoading.value = true
_booksFromFB.value = repository.getBooksFromFB().data!!
if (_booksFromFB.value.isNotEmpty())
_isLoading.value = false
}
}
}

ConstraintLayout in jetpack compose giving strange results

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:

ConstraintLayout not returning any output in jetpack compose

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")
}
}

Some of Text Fields do not update when locale change in composable android

val languages = listOf(
"en" to "English",
"vi" to "VietNam",
)
#Composable
fun LoginScreen(
navController: NavController?,
viewModel: LoginViewModel = hiltViewModel()
) {
val currentLanguageIndex = viewModel.language.observeAsState().value ?: 0
SetLanguage(currentLanguageIndex)
Scaffold() {
MainUI(navController = navController, viewModel = viewModel,index= currentLanguageIndex)
}
}
#Composable
fun SetLanguage(languageIndex: Int) {
val locale = Locale(if (languageIndex == 0) "en" else "vi")
val configuration = LocalConfiguration.current
configuration.setLocale(locale)
val resources = LocalContext.current.resources
resources.updateConfiguration(configuration, resources.displayMetrics)
}
#OptIn(ExperimentalComposeUiApi::class)
//#Preview(device = Devices.AUTOMOTIVE_1024p)
#Composable
fun MainUI(navController: NavController?, viewModel: LoginViewModel,index : Int) {
val keyboardController = LocalSoftwareKeyboardController.current
val image = painterResource(id = R.drawable.ic_login_img)
var emailValue by remember { mutableStateOf("") }
var passwordValue by remember { mutableStateOf("") }
val passwordVisibility = remember { mutableStateOf(false) }
val focusRequesterEmail = remember { FocusRequester() }
val focusRequesterPassword = remember { FocusRequester() }
val scrollState = rememberScrollState()
val checkState = remember {
mutableStateOf(true)
}
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = Color.White),
horizontalAlignment = Alignment.End
) {
// DropdownDemo(viewModel = viewModel,index = index)
val scope = rememberCoroutineScope()
var expanded by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(index) }
Box(
modifier = Modifier
.width(150.dp)
.padding(top = 16.dp, end = 8.dp)
.wrapContentSize(Alignment.TopEnd)
) {
DropDownLabel(languages[selectedIndex].second, onClick = {
expanded = true
})
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.width(150.dp)
) {
languages.forEachIndexed { index, s ->
DropdownMenuItem(
onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
},
) {
DropDownLabel(s.second, onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
})
}
}
}
}
Spacer(modifier = Modifier.height(20.dp))
Column(
modifier = Modifier
.fillMaxSize()
.scrollable(state = scrollState, orientation = Orientation.Vertical),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.height(70.dp)
.width(140.dp),
contentAlignment = Alignment.TopCenter
) {
Image(painter = image, contentDescription = null)
}
Spacer(modifier = Modifier.height(19.dp))
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.hello),
style = MaterialTheme.typography.h4,
)
Spacer(modifier = Modifier.height(10.dp))
Text(
text = stringResource(R.string.enter_credentials_msg),
style = MaterialTheme.typography.body2,
fontSize = 14.sp
)
Spacer(modifier = Modifier.padding(10.dp))
Column(
modifier = Modifier
.fillMaxWidth(0.6f)
.padding(
start = 16.dp,
end = 16.dp
),
horizontalAlignment = Alignment.CenterHorizontally
) {
OutlinedTextField(
value = emailValue,
onValueChange = { emailValue = it },
label = { Text(text = stringResource(id = R.string.email),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
placeholder = { Text(text = stringResource(id = R.string.email),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester = focusRequesterEmail),
colors = TextFieldDefaults.outlinedTextFieldColors(
unfocusedBorderColor = Gray2
),
keyboardActions = KeyboardActions(
onNext = {
CoroutineScope(Default).launch {
keyboardController?.hide()
delay(400)
focusRequesterPassword.requestFocus()
}
}
)
)
Spacer(modifier = Modifier.padding(5.dp))
OutlinedTextField(
value = passwordValue,
colors = TextFieldDefaults.outlinedTextFieldColors(
unfocusedBorderColor = Gray2
),
onValueChange = { passwordValue = it },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
trailingIcon = {
IconButton(onClick = {
passwordVisibility.value = !passwordVisibility.value
}) {
Icon(
painter = painterResource(id = if (passwordVisibility.value) R.drawable.ic_baseline_visibility_24 else R.drawable.ic_baseline_visibility_off_24),
contentDescription = "Visibility button",
)
}
},
label = { Text(stringResource(id = R.string.password),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
placeholder = { Text(text = stringResource(id = R.string.password),
style = MaterialTheme.typography.body2.copy(color = Gray1)) },
singleLine = true,
visualTransformation = if (passwordVisibility.value) VisualTransformation.None
else PasswordVisualTransformation(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester = focusRequesterPassword),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() }
),
)
Spacer(modifier = Modifier.padding(25.dp))
Row(
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier.width(intrinsicSize = IntrinsicSize.Max),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = checkState.value, onCheckedChange = {
checkState.value = it
})
Text(
stringResource(id = R.string.remember_me),
maxLines = 1,
style = MaterialTheme.typography.body2.copy(color = Gray1)
)
}
Box(
contentAlignment = Alignment.CenterEnd,
modifier = Modifier.fillMaxWidth()
) {
OutlinedButton(
onClick = {
// TODO
},
border = BorderStroke(0.5.dp, BlueLight),
shape = RoundedCornerShape(8.dp)
) {
Text(
text = stringResource(id = R.string.sign_in),
fontSize = 20.sp,
style = MaterialTheme.typography.button.copy(color = BlueLight)
)
}
}
}
}
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 8.dp),
contentAlignment = Alignment.BottomCenter
) {
Text(
text = stringResource(id = R.string.skytech_software_solutions_pvt_ltd),
style = MaterialTheme.typography.caption.copy(color = Gray1),
)
}
}
}
}
#Composable
fun DropdownDemo(viewModel: LoginViewModel,index: Int) {
val scope = rememberCoroutineScope()
var expanded by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(index) }
Box(
modifier = Modifier
.width(150.dp)
.padding(top = 16.dp, end = 8.dp)
.wrapContentSize(Alignment.TopEnd)
) {
DropDownLabel(languages[selectedIndex].second, onClick = {
expanded = true
})
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.width(150.dp)
) {
languages.forEachIndexed { index, s ->
DropdownMenuItem(
onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
},
) {
DropDownLabel(s.second, onClick = {
selectedIndex = index
expanded = false
scope.launch {
viewModel.saveLocale(index)
}
})
}
}
}
}
}
#Composable
fun DropDownLabel(labelName: String, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_language_24),
contentDescription = "language"
)
Spacer(modifier = Modifier.width(10.dp))
Text(
labelName, modifier = Modifier
.fillMaxWidth()
)
}
}
when we change the language UI does not update some of text like sign in button and Edit text. but normal text are changing. how do we over come this situation. I have used mutable live data for the viewModel to UI. when we change language and kill the application and restart, then update all the labels.

How to open datePicker on Button Click ? in jetpack compose

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 .

Categories

Resources