Я строю приложение для Android, используя JetPack Compose и архитектуру MVVM с рукояти для инъекции зависимостей. В моем регистрационном экране у меня есть кнопка регистрации, которая должна инициировать процесс регистрации через registerviewmodel.register (...), но ** Нажатие кнопки ничего не делает, без перехода к следующему экрану. < /P>
Вот что у меня есть: < /p>
Components. Обработка, входы текста и кнопку Нажмите запуск COROUTINE.
regeGisteractivity.KT: собирает состояние из регистрации. Заполнение всех полей.
Нажимать на кнопку, запуская Coroutine в onclick лямбда.
журналы, такие как «Запуск регистрации внутри Coroutine» и «>>> Регистрационная функция, называемая наружным Coroutine». UserRepository.register (...) может не стрелять, или ViewModel неправильно обновляет _ RegisterState, или, возможно, пользовательский интерфейс не реагирует на изменения состояния. Вызов.
Проверяет подключение к бэкэнд и регистрируется как достижимо.
проверил, что реестр собирается как в Registerscreen.kt, так и в регистрации. Kt.
Что мне нужна помощь с
может кто -нибудь помочь мне идентифицировать, почему кнопка регистрации не завершает поток регистрации или почему не обновляется по всему поток. Может быть, может пойти не так с Coroutine, состоянием ViewModel или Logic обратного вызова.package com.example.eventmanagement.ui
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.derivedStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.eventmanagement.viewmodel.RegisterViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RegisterScreen(
viewModel: RegisterViewModel,
onRegisterSuccess: () -> Unit,
onLoginClick: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
// Collect state from ViewModel
val registerState by viewModel.registerState.collectAsState()
val selectedRole by viewModel.selectedRole.collectAsState()
// Use remember to prevent unnecessary recompositions
val name = remember { mutableStateOf("") }
val email = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
val confirmPassword = remember { mutableStateOf("") }
// Show loading state
val isLoading = remember(registerState) {
registerState is RegisterViewModel.RegisterState.Loading
}
// Derive button enabled state from current values
val buttonEnabled = derivedStateOf {
!isLoading && selectedRole != null && name.value.trim().isNotEmpty() &&
email.value.trim().isNotEmpty() && password.value.isNotEmpty() &&
confirmPassword.value.isNotEmpty()
}
// Password visibility states
val passwordVisibility = remember { mutableStateOf(false) }
val confirmPasswordVisibility = remember { mutableStateOf(false) }
// Handle register state changes
LaunchedEffect(registerState) {
when (registerState) {
is RegisterViewModel.RegisterState.Success -> {
withContext(Dispatchers.Main.immediate) {
Toast.makeText(context, "Registration Successful", Toast.LENGTH_SHORT).show()
onRegisterSuccess()
}
}
is RegisterViewModel.RegisterState.Error -> {
val errorMessage = (registerState as RegisterViewModel.RegisterState.Error).message
withContext(Dispatchers.Main.immediate) {
Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
}
}
else -> {}
}
}
// Test backend connectivity on first load
LaunchedEffect(Unit) {
android.util.Log.d("RegisterScreen", "Testing backend connectivity...")
// This will be handled by the UserRepository when registration is attempted
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Create Account") }
)
},
snackbarHost = { SnackbarHost(snackbarHostState) }
) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Register",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
// Role Selection Section
Text(
text = "Are you an owner or client?",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.setRole("Owner") },
modifier = Modifier
.weight(1f)
.height(48.dp),
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedRole == "Owner")
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary
)
) {
Text("Owner")
}
Button(
onClick = { viewModel.setRole("Client") },
modifier = Modifier
.weight(1f)
.height(48.dp),
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedRole == "Client")
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary
)
) {
Text("Client")
}
}
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = name.value,
onValueChange = { name.value = it },
label = { Text("Name") },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
)
)
OutlinedTextField(
value = email.value,
onValueChange = { email.value = it },
label = { Text("Email") },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
)
)
OutlinedTextField(
value = password.value,
onValueChange = { password.value = it },
label = { Text("Password") },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Next
),
visualTransformation = if (passwordVisibility.value) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisibility.value = !passwordVisibility.value }) {
Icon(
imageVector = if (passwordVisibility.value) Icons.Default.Visibility else Icons.Default.VisibilityOff,
contentDescription = if (passwordVisibility.value) "Hide password" else "Show password"
)
}
}
)
OutlinedTextField(
value = confirmPassword.value,
onValueChange = { confirmPassword.value = it },
label = { Text("Confirm Password") },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 32.dp),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
visualTransformation = if (confirmPasswordVisibility.value) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { confirmPasswordVisibility.value = !confirmPasswordVisibility.value }) {
Icon(
imageVector = if (confirmPasswordVisibility.value) Icons.Default.Visibility else Icons.Default.VisibilityOff,
contentDescription = if (confirmPasswordVisibility.value) "Hide password" else "Show password"
)
}
}
)
// Help text
if (!buttonEnabled.value && !isLoading) {
val missingFields = mutableListOf()
if (selectedRole == null) missingFields.add("role")
if (name.value.trim().isEmpty()) missingFields.add("name")
if (email.value.trim().isEmpty()) missingFields.add("email")
if (password.value.isEmpty()) missingFields.add("password")
if (confirmPassword.value.isEmpty()) missingFields.add("confirm password")
Text(
text = "Please fill in: ${missingFields.joinToString(", ")}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
)
}
Text(
text = "Debug: Role=$selectedRole, Loading=$isLoading, Name=${name.value.isNotEmpty()}, Email=${email.value.isNotEmpty()}, Password=${password.value.isNotEmpty()}, Confirm=${confirmPassword.value.isNotEmpty()}, ButtonEnabled=${buttonEnabled.value}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Button(
onClick = {
scope.launch {
android.util.Log.d("RegisterScreen", "Launching register inside coroutine")
if (selectedRole == null) {
Toast.makeText(context, "Please select a role (Owner or Client)", Toast.LENGTH_SHORT).show()
return@launch
}
if (name.value.trim().isEmpty()) {
Toast.makeText(context, "Please enter your name", Toast.LENGTH_SHORT).show()
return@launch
}
if (email.value.trim().isEmpty()) {
Toast.makeText(context, "Please enter your email", Toast.LENGTH_SHORT).show()
return@launch
}
if (password.value.isEmpty()) {
Toast.makeText(context, "Please enter a password", Toast.LENGTH_SHORT).show()
return@launch
}
if (password.value != confirmPassword.value) {
Toast.makeText(context, "Passwords do not match", Toast.LENGTH_SHORT).show()
return@launch
}
viewModel.register(
name.value.trim(),
email.value.trim(),
password.value
)
}
},
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
enabled = buttonEnabled.value,
colors = ButtonDefaults.buttonColors(
containerColor = if (buttonEnabled.value) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
)
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary
)
} else {
Text(
text = if (buttonEnabled.value) "Register" else "Fill all fields to register",
color = if (buttonEnabled.value) MaterialTheme.colorScheme.onPrimary
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
)
}
}
TextButton(
onClick = onLoginClick,
modifier = Modifier.padding(top = 8.dp)
) {
Text("Already have an account? Login")
}
}
}
}
< /code>
regegisteractivity.kt:
package com.example.eventmanagement.ui
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material3.SnackbarHostState
import androidx.lifecycle.lifecycleScope
import com.example.eventmanagement.MainActivity
import com.example.eventmanagement.viewmodel.RegisterViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@AndroidEntryPoint
class RegisterActivity : AppCompatActivity() {
private val viewModel: RegisterViewModel by viewModels()
private val snackbarHostState = SnackbarHostState()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set content first to show UI immediately
setContent {
RegisterScreen(
viewModel = viewModel,
onRegisterSuccess = { /* Handled by state collection */ },
onLoginClick = { navigateToLoginActivity() }
)
}
// Start background operations after UI is shown
lifecycleScope.launch(Dispatchers.IO) {
try {
val isLoggedIn = viewModel.isLoggedIn()
if (isLoggedIn) {
withContext(Dispatchers.Main.immediate) {
navigateToMainActivity()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main.immediate) {
showError("Failed to check login status: ${e.message}")
}
}
}
// Collect state changes in a separate coroutine
lifecycleScope.launch(Dispatchers.IO) {
try {
android.util.Log.d("RegisterActivity", "Starting to collect register state changes")
viewModel.registerState.collectLatest { state ->
android.util.Log.d("RegisterActivity", "State changed to: $state")
when (state) {
is RegisterViewModel.RegisterState.Success -> {
android.util.Log.d("RegisterActivity", "Registration successful, navigating to MainActivity")
withContext(Dispatchers.Main.immediate) {
navigateToMainActivity()
}
}
is RegisterViewModel.RegisterState.Error -> {
android.util.Log.d("RegisterActivity", "Registration error: ${state.message}")
withContext(Dispatchers.Main.immediate) {
showError(state.message)
}
}
is RegisterViewModel.RegisterState.Loading -> {
android.util.Log.d("RegisterActivity", "Registration loading...")
}
else -> {
android.util.Log.d("RegisterActivity", "Other state: $state")
}
}
}
} catch (e: Exception) {
android.util.Log.e("RegisterActivity", "Failed to collect state changes: ${e.message}")
withContext(Dispatchers.Main.immediate) {
showError("Failed to collect state changes: ${e.message}")
}
}
}
}
private fun navigateToMainActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
}
private fun navigateToLoginActivity() {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
private fun showError(message: String) {
lifecycleScope.launch(Dispatchers.Main.immediate) {
snackbarHostState.showSnackbar(message)
}
}
}
< /code>
regegisterviewmodel.kt:
package com.example.eventmanagement.viewmodel
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class RegisterViewModel @Inject constructor(
application: Application,
private val userRepository: UserRepository
) : AndroidViewModel(application) {
private val _registerState = MutableStateFlow(RegisterState.Idle)
val registerState: StateFlow = _registerState
private val _selectedRole = MutableStateFlow(null)
val selectedRole: StateFlow = _selectedRole
suspend fun isLoggedIn(): Boolean {
return try {
userRepository.isLoggedIn()
} catch (e: Exception) {
withContext(Dispatchers.Main.immediate) {
_registerState.value = RegisterState.Error(e.message ?: "Failed to check login status")
}
false
}
}
fun setRole(role: String) {
_selectedRole.value = role
}
fun register(name: String, email: String, password: String) {
android.util.Log.d("RegisterViewModel", ">>> register function called OUTSIDE coroutine")
viewModelScope.launch {
android.util.Log.d("RegisterViewModel", ">>> INSIDE coroutine launch block")
try {
android.util.Log.d("RegisterViewModel", "Setting state to Loading")
_registerState.value = RegisterState.Loading
val isBackendReachable = userRepository.testBackendConnectivity()
android.util.Log.d("RegisterViewModel", "Backend connectivity test result: $isBackendReachable")
if (!isBackendReachable) {
_registerState.value = RegisterState.Error("Cannot connect to server. Please check your internet connection or try again later.")
return@launch
}
if (!validateInput(name, email, password)) {
android.util.Log.d("RegisterViewModel", "Input validation failed")
_registerState.value = RegisterState.Error("Invalid input")
return@launch
}
if (_selectedRole.value == null) {
android.util.Log.d("RegisterViewModel", "No role selected")
_registerState.value = RegisterState.Error("Please select a role")
return@launch
}
android.util.Log.d("RegisterViewModel", "Calling userRepository.register")
userRepository.register(
name = name,
email = email,
password = password,
role = _selectedRole.value!!,
onSuccess = {
android.util.Log.d("RegisterViewModel", "Registration successful")
_registerState.value = RegisterState.Success
},
onFailure = { error ->
android.util.Log.d("RegisterViewModel", "Registration failed: $error")
_registerState.value = RegisterState.Error((error ?: "Registration failed").toString())
}
)
} catch (e: Exception) {
android.util.Log.e("RegisterViewModel", "Exception in register: ${e.message}")
_registerState.value = RegisterState.Error(e.message ?: "Registration failed")
}
}
}
private fun isNameValid(name: String): Boolean {
return name.trim().length >= 2
}
private fun isEmailValid(email: String): Boolean {
val emailRegex = Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$")
return emailRegex.matches(email.trim())
}
private fun isPasswordValid(password: String): Boolean {
return password.length >= 6
}
private fun validateInput(name: String, email: String, password: String): Boolean {
android.util.Log.d("RegisterViewModel", "Validating input - Name: '$name' (${name.trim().length}), Email: '$email', Password: '${"*".repeat(password.length)}' (${password.length})")
if (!isNameValid(name)) {
android.util.Log.d("RegisterViewModel", "Name validation failed: ${name.trim().length} < 2")
_registerState.value = RegisterState.Error("Name must be at least 2 characters long")
return false
}
if (!isEmailValid(email)) {
android.util.Log.d("RegisterViewModel", "Email validation failed: $email")
_registerState.value = RegisterState.Error("Please enter a valid email address")
return false
}
if (!isPasswordValid(password)) {
android.util.Log.d("RegisterViewModel", "Password validation failed: length ${password.length} < 6")
_registerState.value = RegisterState.Error("Password must be at least 6 characters long")
return false
}
android.util.Log.d("RegisterViewModel", "Input validation passed")
return true
}
sealed class RegisterState {
object Idle : RegisterState()
object Loading : RegisterState()
object Success : RegisterState()
data class Error(val message: String) : RegisterState()
}
}
Подробнее здесь: https://stackoverflow.com/questions/797 ... compose-an
Почему моя кнопка регистрации не работает в моем приложении Android, используя JetPack Compose и MVVM? ⇐ Android
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение
-
-
В чем разница между mvvm с чистой архитектурой и mvvm без чистой архитектуры в Android?
Anonymous » » в форуме Android - 0 Ответы
- 30 Просмотры
-
Последнее сообщение Anonymous
-