Reputation: 159
i have a SignUp page to store user data, i'm using HorizontalPager to view TextFields. At the last page when user click Submit if user didn't provide correct password and email, the AlertDialog pop up.
The bug is when i dismiss AlertDialog and navigate back from current page(last page in HorizontalPager) to first one, the AlertDialog show.
i have the SignupPage iniside a HorizontalPager along with a StartPage and LoginPage. to achieve Swappable effect between pages.
Also, when i go to LoginPage the AlertDialog pops up although i didn't press anything or entering anydata.
This is MyNavigation.kt
@Composable
fun MyAppNavigation(
modifier: Modifier, authViewModel: AuthViewModel
) {
val pages = registerInfoList.size
val pagerState = rememberPagerState(pageCount = { pages })
val scope = rememberCoroutineScope()
Column(
modifier = modifier.fillMaxSize()
) {
HorizontalPager(
state = pagerState, userScrollEnabled = false
) { page ->
when (page) {
0 -> StartPage(modifier = modifier,
toSignUp = {
scope.launch {
pagerState.animateScrollToPage(2)
}
}, toLogin = {
scope.launch {
pagerState.animateScrollToPage(1)
}
}, authViewModel = authViewModel
)
1 -> LoginPage(modifier = modifier,
toHome = {
scope.launch {
pagerState.animateScrollToPage(3)
}
}, toSignUp = {
scope.launch {
pagerState.animateScrollToPage(2)
}
},
toStart = {
scope.launch {
pagerState.animateScrollToPage(0)
}
}, authViewModel = authViewModel
)
2 -> SignupPage(
modifier = modifier,
toStart = {
scope.launch {
pagerState.animateScrollToPage(0)
}
},
toHome = {
scope.launch {
pagerState.animateScrollToPage(3)
}
},
authViewModel = authViewModel
)
......
}
}
}
}
LoginPage.kt
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Composable
fun LoginPage(
modifier: Modifier = Modifier,
//navController: NavController,
toSignUp: () -> Unit,
toHome: () -> Unit,
toStart: () -> Unit,
authViewModel: AuthViewModel
) {
val shouldShowDialog = remember {
mutableStateOf(false)
}
var email by remember {
mutableStateOf("")
}
var password by remember {
mutableStateOf("")
}
var showPassword by remember {
mutableStateOf(false)
}
val isFormValid = remember {
derivedStateOf {
email.isNotEmpty() && password.isNotEmpty()
}
}
var errorMessage by remember {
mutableStateOf("")
}
if (shouldShowDialog.value) {
AlertDialog(shouldShowDialog = shouldShowDialog, message = errorMessage)
}
val authState = authViewModel.authState.observeAsState()
LocalContext.current
LaunchedEffect(authState.value) {
when (authState.value) {
is AuthState.Authenticated -> toHome()
is AuthState.Error.InvalidCredentialsError -> {
errorMessage = (authState.value as AuthState.Error.InvalidCredentialsError).message
shouldShowDialog.value = true
}
is AuthState.Error.NetworkError -> {
errorMessage = (authState.value as AuthState.Error.NetworkError).message
}
else -> Unit
}
}
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
"Log in",
fontWeight = FontWeight.Bold
)
},
navigationIcon = {
IconButton(onClick = {toStart()}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Localized description"
)
}
}, colors = TopAppBarColors(
containerColor = colorResource(id = R.color.black),
titleContentColor = Color.White,
actionIconContentColor = Color.White,
navigationIconContentColor = Color.White,
scrolledContainerColor = colorResource(id = R.color.black)
)
)
},
) { innerPadding ->
Column(
modifier = modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp)
.background(MaterialTheme.colorScheme.background),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start, // Aligns text to the left
text = "Email or username",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.size(4.dp))
TextField(......................
)
Spacer(modifier = Modifier.size(32.dp))
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start,
text = "Password",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.size(4.dp))
TextField(.................
)
Text(
text = errorMessage,
color = Color.White,
fontSize = 13.sp,
textAlign = TextAlign.Start
)
Spacer(modifier = Modifier.height(32.dp))
var colorButton by remember { mutableStateOf(Color.White) }
Button(..................., enabled = isFormValid.value, onClick = {
}) {
................
}
Spacer(modifier = Modifier.height(8.dp))
var color by remember { mutableStateOf(Color.White) }
TextButton(modifier = Modifier
.fillMaxWidth()
.bounceClickEffect()
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
color = Color.Gray
}
MotionEvent.ACTION_UP -> {
color = Color.White
toSignUp()
}
}
true
},
enabled = isFormValid.value,
onClick = {
}) {
..................
}
}
}
}
SignupPage.kt
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun SignupPage(
modifier: Modifier = Modifier,
//navController: NavController,
toStart: () -> Unit, toHome: () -> Unit, authViewModel: AuthViewModel
) {
var email by remember {
mutableStateOf("")
}
var password by remember { mutableStateOf("") }
var dateOfBirth by remember { mutableStateOf("") }
var gender by remember { mutableStateOf("") }
var isExpanded by remember { mutableStateOf(false) }
var userName by remember { mutableStateOf("") }
var colorButton by remember { mutableStateOf(Color.White) }
val pages = registerInfoList.size
val pagerState = rememberPagerState(pageCount = { pages })
val scope = rememberCoroutineScope()
var showPassword by remember {
mutableStateOf(false)
}
val authState = authViewModel.authState.observeAsState()
// To handle whether we can move to the next page
val isFormValid = remember {
derivedStateOf {
when (pagerState.currentPage) {
0 -> {
email.isNotEmpty()
}
1 -> {
password.isNotEmpty()
}
2 -> {
dateOfBirth.isNotEmpty()
}
3 -> {
gender.isNotEmpty()
}
4 -> {
userName.isNotEmpty()
}
else -> {
false
}
}
}
}
var errorMessage by remember {
mutableStateOf("")
}
val shouldShowDialog = remember {
mutableStateOf(false)
}
if (shouldShowDialog.value) {
AlertDialog(shouldShowDialog = shouldShowDialog, message = errorMessage)
}
val keyboardController = LocalSoftwareKeyboardController.current
val context = LocalContext.current
LaunchedEffect(authState.value) {
when (authState.value) {
is AuthState.Authenticated -> {shouldShowDialog.value = false
toHome()}
is AuthState.Error.InvalidCredentialsError -> {
errorMessage = (authState.value as AuthState.Error.InvalidCredentialsError).message
shouldShowDialog.value = true
}
is AuthState.Error.NetworkError -> {
errorMessage = (authState.value as AuthState.Error.NetworkError).message
}
else -> shouldShowDialog.value = false
}
}
DisposableEffect(Unit) {
onDispose {
shouldShowDialog.value = false // Reset when leaving this screen
}
}
LaunchedEffect(pagerState.currentPage) {//------> this where the crash happen
when (pagerState.currentPage) {
2 -> keyboardController?.hide()
3 -> keyboardController?.hide()
}
}
Scaffold(
topBar = {
CenterAlignedTopAppBar(title = {
Text(
"Create account", fontWeight = FontWeight.Bold
)
}, navigationIcon = {
IconButton(onClick = {
if (shouldShowDialog.value) {
shouldShowDialog.value = false // Close dialog if open
}
else {
if (pagerState.currentPage == 0) {
toStart()
} else {
scope.launch {
pagerState.animateScrollToPage(pagerState.currentPage - 1)
}
}
}
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Localized description"
)
}
}
)
)
},
) { innerPadding ->
Column(
modifier = modifier
.fillMaxSize()
.padding(innerPadding)
.padding(8.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
HorizontalPager(
state = pagerState, userScrollEnabled = false
) { index ->
Column(
modifier = Modifier
.padding(8.dp)
.wrapContentHeight()
.background(MaterialTheme.colorScheme.background),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start, // Aligns text to the left
text = registerInfoList[index].title,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.size(4.dp))
when (index) {
0 -> // email and name
TextField(
modifier = Modifier
.fillMaxWidth()
// .focusRequester(emailFocusRequester),
,value = email,
colors = TextFieldDefaults.colors(
unfocusedContainerColor = colorResource(id = R.color.gray),
cursorColor = colorResource(id = R.color.light_gray),
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
focusedContainerColor = colorResource(id = R.color.moon_gray),
disabledLabelColor = colorResource(id = R.color.moon_gray),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
onValueChange = { newValue ->
email = newValue
},
shape = RoundedCornerShape(8.dp),
singleLine = true,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next
)
)
1 -> {
...................
}
2 -> {
DatePicker { date ->
dateOfBirth = date.let {
SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(it)
}
}
}
3 -> {..................
}
4 -> // email and name
TextField(...................
}
Spacer(modifier = Modifier.size(4.dp))
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start, // Aligns text to the left
text = registerInfoList[index].message,
fontSize = 10.sp,
color = Color.White
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Button(modifier = Modifier
.bounceClickEffect()
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
colorButton = Color.Gray
}
MotionEvent.ACTION_UP -> {
if (isFormValid.value) {
// Update page state first
scope.launch {
pagerState.animateScrollToPage(pagerState.currentPage + 1)
}
// Then set color back to white for visual feedback
colorButton = Color.White
if (pagerState.currentPage == registerInfoList.size - 1) {
authViewModel.signup(email, password)
}
}
}
}
true
}, ..............
), enabled = isFormValid.value, onClick = {}) {
Text(text = "Log in", fontSize = 16.sp)
}
}
}
}
This AuthViewModel.kt
class AuthViewModel(
private val networkConnectivityObserver: NetworkConnectivityObserver
) : ViewModel() {
private val auth : FirebaseAuth = FirebaseAuth.getInstance()
private val _authState = MutableLiveData<AuthState>()
val authState : LiveData<AuthState> = _authState
fun checkAuthStatus(){
if(auth.currentUser==null){
_authState.value = AuthState.Unauthenticated
}else{
_authState.value = AuthState.Authenticated
}
}
fun login(email: String, password: String) {
if (email.isEmpty() || password.isEmpty()) {
_authState.value = AuthState.Error.InvalidCredentialsError("Email/Password can't be empty.")
return
}
_authState.value = AuthState.Loading
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
_authState.value = AuthState.Authenticated
} else {
// Handle login failure
val exception = task.exception
if (exception is FirebaseNetworkException) {
_authState.value =
AuthState.Error.NetworkError("You're offline. Check you connection and try again.")
// Network error occurred
// Show an error message to the user and suggest retrying
} else if (exception is FirebaseAuthException){
_authState.value =
AuthState.Error.InvalidCredentialsError("This email and password combination is incorrect.")
}
}
}
}
fun signup(email: String, password: String){
if(email.isEmpty() || password.isEmpty() ){
_authState.value = AuthState.Error.InvalidCredentialsError("Email/Password can't be empty.")
return
}
_authState.value = AuthState.Loading
auth.createUserWithEmailAndPassword(email,password)
.addOnCompleteListener {task ->
if(task.isSuccessful){
_authState.value = AuthState.Authenticated
}else {
_authState.value = AuthState.Error.InvalidCredentialsError(task.exception?.message?:"Something went wrong")
Log.d("HEEEEEEEEEEEELLO",task.exception?.message?:"Something went wrong")
}
}
}
fun signout(){
auth.signOut()
_authState.value = AuthState.Unauthenticated
}
}
sealed class AuthState{
data object Authenticated : AuthState()
data object Unauthenticated : AuthState()
data object Loading : AuthState()
sealed class Error : AuthState() {
data class NetworkError(val message: String) : Error()
data class InvalidCredentialsError(val message: String) : Error()
}
}
This AlerDialog.kt
@Composable
fun AlertDialog(shouldShowDialog: MutableState<Boolean>, message: String) {
if (shouldShowDialog.value) {
Dialog(onDismissRequest = { shouldShowDialog.value = false
Log.d("Bad",shouldShowDialog.value.toString() )}) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(250.dp)
.padding(16.dp),
shape = RoundedCornerShape(16.dp)
) {
Column(
Modifier
.fillMaxSize()
.background(Color.White),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = message, color = Color.Black,
textAlign = TextAlign.Center,
fontSize = 13.sp,
modifier = Modifier
.padding(16.dp)
)
Spacer(modifier = Modifier
.size(8.dp))
Button( onClick = { shouldShowDialog.value = false }) {
Text(text = "OK", color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.Bold,modifier = Modifier
.padding(4.dp))
}
}
}
}
}
}
Upvotes: 1
Views: 34