주석에 달아놓은 숫자 순서 따라가면서 읽어보면 이해가 더 쉽다.
SignUpActivity.kt
package com.example.startactitivity.signup
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import com.example.startactitivity.HomeActivity
import com.example.startactitivity.SignInActivity
import com.example.startactitivity.User
import com.example.startactitivity.UserDatabase
import com.example.startactitivity.databinding.ActivitySignupBinding
class SignUpActivity : AppCompatActivity() {
private lateinit var resultLauncher: ActivityResultLauncher<Intent>
/** [0] 숙련 주차에 와서 처음 배운 바인딩을 이용해서 코드를 작성하였다. **/
private lateinit var binding: ActivitySignupBinding
private var accessPath: Int = 0
private var checkName: String? = ""
private var checkId: String? = ""
private var checkPassword: String? = ""
val editTextArray by lazy {
arrayOf(binding.etName, binding.etId, binding.etPw)
}
/** [1] 가장 먼저 액티비티 클래스와 뷰모델 클래스를 연결해 주어야 한다. **/
private val viewModel by lazy {
ViewModelProvider(this@SignUpActivity)[SignUpViewModel::class.java]
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySignupBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initView()
/** [3] 뷰모델 초기 함수를 만들어준다. **/
initViewModel()
}
private fun initView() {
if (intent.hasExtra("edit")) {
binding.btnSignup.text = "회원 수정"
accessPath = 1
}
/** [5] 가장 먼저 initView 함수가 실행 될것이거 그에 따라 아래 3개의 함수가 실행될 것이다. **/
setTextChangedListener()
setOnFocusChangedListener()
onClickBtnSignUp()
}
/** [4] 이 함수도 = with(viewModel)을 적어주어야 한다.
* 이 함수에서 뷰모델을 관찰할거라고 말해주는 부분인 것 같다.**/
private fun initViewModel() = with(viewModel) {
/** [8] 어느 EditText에 입력값이 변경되었는지 전달 해 주었다.
* 이제 이 새로 입력되거나 지워진 값이 로그인 유효성 검사를 해주기 위해 setErrorMessage() 함수를 호출한다.
* 그리고 어느 EditText인지도 it을 통해 전달한다.
* 추가적으로 입력 정보가 모두 유효하면 로그인 버튼이 활성화되어야 하기 때문에
* setConfrimButtenEnable() 함수도 실행해 준다.**/
editTextTextChange.observe(this@SignUpActivity) {
setErrorMessage(it)
setConfirmButtonEnable()
}
/** [12] SignUpErrorMessage 타입에서 문자열로 바꾸어준다.
* EditText의 type과 그 문자열을 getNullIfValid() 함수를 호출하여 전달한다.**/
nameErrorMessage.observe(this@SignUpActivity) {
getNullIfValid("name", getString(it.message))
}
idErrorMessage.observe(this@SignUpActivity) {
getNullIfValid("id", getString(it.message))
}
passwordErrorMessage.observe(this@SignUpActivity) {
getNullIfValid("password", getString(it.message))
}
/** [15] checkName에 저장되어있던 값을 EditText가 유효하지 않으면 띄울 수 있게 .error을 사용해준다.
* 그리고 나중에 이름,비밀번호, 아이디가 유효할 때 로그인 버튼을 활성화 해주기 위해 SignUpActivity에
* 있는 checkName 변수가 이름 에딧텍스트가 유효한 경우 null값을 가지게 해준다.**/
checkName.observe(this@SignUpActivity) {
binding.etName.error = it
this@SignUpActivity.checkName = it
}
checkId.observe(this@SignUpActivity) {
binding.etId.error = it
this@SignUpActivity.checkId = it
}
checkPassword.observe(this@SignUpActivity) {
binding.etPw.error = it
this@SignUpActivity.checkPassword = it
}
enableConfrimButton.observe(this@SignUpActivity) {
binding.btnSignup.isEnabled = it
}
/** [18] [8]번과 동일**/
editTextOnFocus.observe(this@SignUpActivity) {
setErrorMessage(it)
setConfirmButtonEnable()
}
}
private fun onClickBtnSignUp() {
resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
intent.putExtra("id", binding.etId.text.toString())
intent.putExtra("password", binding.etPw.text.toString())
setResult(RESULT_OK, intent)
finish()
}
}
binding.btnSignup.setOnClickListener {
when (accessPath) {
0 -> {
UserDatabase.addUser(
User(
binding.etName.text.toString(),
binding.etId.text.toString(),
binding.etPw.text.toString()
)
)
val intent = Intent(this, SignInActivity::class.java).apply {
putExtra("id", binding.etId.text.toString())
putExtra("password", binding.etPw.text.toString())
}
setResult(RESULT_OK, intent)
resultLauncher.launch(intent)
}
1 -> {
UserDatabase.editUserData(
User(
binding.etName.text.toString(),
binding.etId.text.toString(),
binding.etPw.text.toString()
)
)
val intent = Intent(this, HomeActivity::class.java).apply {
putExtra("id", binding.etId.text.toString())
putExtra("password", binding.etPw.text.toString())
}
setResult(RESULT_OK, intent)
resultLauncher.launch(intent)
}
}
}
}
/** [6] setTextChangedListener은 말 그대로 입력된 값이 변할 때마다 호출 되는 함수이다.
* 함수가 실행 되면 뷰모델에 있는 같은 이름의 함수를 호출한다.
* 이때 editTextArray를 넘겨준다.
* EditTextArray는 이름/아이디/비밀번호에 입력된 값을 모두 가지고 있다.**/
private fun setTextChangedListener() {
viewModel.setTextChangedListener(editTextArray)
}
/** [16] setOnFocusChangedListner은 EditText가 활성화 되어있는지 확인해주는 함수이다.
* setTextChangedListener와 마찬가지로 editTextArray를 넘겨준다.**/
private fun setOnFocusChangedListener() {
viewModel.setOnFocusChangedListener(editTextArray)
}
/** [10] 뷰모델에 있는 이름 유효성 검사 로직을 가진 함수를 호출해준다.
* 그리고 EditText에 적혀있는 입력값을 문자열로 바꾸어 전달한다.**/
private fun getMessageValidName() {
viewModel.setMessageValidName(binding.etName.text.toString())
}
private fun getMessageValidId() {
viewModel.setMessageValidId(binding.etId.text.toString())
}
private fun getMessageValidPassword() {
viewModel.setMessageValidPassword(binding.etPw.text.toString())
}
/** [13] 받은 값 그대로 뷰모델에 있는 setNullIfValid 함수로 바로 전해준다.
* editText에 Error 함수를 사용하여 유효성 검사를 하고 싶은데 유효한 경우가 현재 Null이 아니고 empty String이어서
* empty string을 null로 바꾸어주는 함수이다.**/
private fun getNullIfValid(type: String, message: String) {
viewModel.setNullIfValid(type, message)
}
private fun setConfirmButtonEnable() {
viewModel.enableConfirmButton(checkName, checkId, checkPassword)
}
/** [9] 어떤 EditText가 전달되었느냐에 따라 이름, 아이디 그리고 비밀번호의 유효성검사를 해준다.
* 이름 유효성 검사로 가보자!**/
private fun setErrorMessage(type: String?) {
when (type) {
"name" -> getMessageValidName()
"id" -> getMessageValidId()
"password" -> getMessageValidPassword()
}
}
}
SignUpViewModel.kt
package com.example.startactitivity.signup
import android.widget.EditText
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.startactitivity.signup.SignUpValidExtension.includeAlphabetAndNumber
import com.example.startactitivity.signup.SignUpValidExtension.includeKorean
import com.example.startactitivity.signup.SignUpValidExtension.includeSpecialCharacters
/** [2] 뷰모델 클래스는 뷰모델을 상속받는다 (당연히...**/
class SignUpViewModel : ViewModel() {
private val _nameErrorMessage: MutableLiveData<SignUpErrorMessage> = MutableLiveData()
private val _idErrorMessage: MutableLiveData<SignUpErrorMessage> = MutableLiveData()
private val _passwordErrorMessage: MutableLiveData<SignUpErrorMessage> = MutableLiveData()
private val _checkName: MutableLiveData<String?> = MutableLiveData()
private val _checkId: MutableLiveData<String?> = MutableLiveData()
private val _checkPassword: MutableLiveData<String?> = MutableLiveData()
private val _enableConfirmButton: MutableLiveData<Boolean> = MutableLiveData()
private val _editTextOnFocus: MutableLiveData<String?> = MutableLiveData()
private val _editTextTextChange: MutableLiveData<String?> = MutableLiveData()
val nameErrorMessage: LiveData<SignUpErrorMessage> get() = _nameErrorMessage
val idErrorMessage: LiveData<SignUpErrorMessage> get() = _idErrorMessage
val passwordErrorMessage: LiveData<SignUpErrorMessage> get() = _passwordErrorMessage
val checkName: LiveData<String?> get() = _checkName
val checkId: LiveData<String?> get() = _checkId
val checkPassword: LiveData<String?> get() = _checkPassword
val enableConfrimButton: LiveData<Boolean> get() = _enableConfirmButton
val editTextOnFocus: LiveData<String?> get() = _editTextOnFocus
val editTextTextChange: LiveData<String?> get() = _editTextTextChange
/** [11] 유효성 검사에 필요한 문자열들이 모두 SignUpErrorMessage 클래스에 들어있다.
* 여기서 문자열을 바로 가져오면 뷰모델에 뷰를 가져오는 것이므로 MVVM을 따르지 않는 것이다.
* 따라서 변수의 타입을 SignUpErrorMessage로 해주었다.
* SignUpErrorMessage.NULL이 입력값이 유효한 경우이고 SignUpErrorMessage.NULL 은 비어있는 문자열이다.**/
fun setMessageValidName(text: String) {
_nameErrorMessage.value = when {
text.isBlank() -> SignUpErrorMessage.EMPTY_NAME
text.includeKorean() -> SignUpErrorMessage.NULL
else -> SignUpErrorMessage.INVALID_NAME
}
}
fun setMessageValidId(text: String) {
_idErrorMessage.value = when {
text.isBlank() -> SignUpErrorMessage.EMPTY_ID
text.includeAlphabetAndNumber() -> SignUpErrorMessage.NULL
else -> SignUpErrorMessage.INVALID_ID
}
}
fun setMessageValidPassword(text: String) {
_passwordErrorMessage.value = when {
text.isBlank() -> SignUpErrorMessage.EMPTY_PASSWORD
text.includeSpecialCharacters() -> SignUpErrorMessage.NULL
else -> SignUpErrorMessage.INVALID_PASSWORD
}
}
/** [14] 타입에 따라 각각 checkName, checkId, checkPassword에 들어가는 값을 지정해준다.
* 유효하면(isEmpty()인경우) null값을 부여해주고 아니면 원래 있었던 text를 전달한다.
* 이 유효성 검사 작업이 모든 EditText에 동일하게 적용되기 때문에 뷰모델 안에 editTextValidation 함수를 만들었다.**/
fun setNullIfValid(type: String, text: String) {
when (type) {
"name" -> {
_checkName.value = editTextValidation(text)
}
"id" -> {
_checkId.value = editTextValidation(text)
}
"password" -> {
_checkPassword.value = editTextValidation(text)
}
}
}
/** [15] 앞서 말했듯이 문자열이 비어있으면 null을 부여 아닌 경우에는 원래 있었던 문자열을 리턴한다.**/
private fun editTextValidation(text:String):String? {
val validate = when {
text.isEmpty() -> null
else -> text
}
return validate
}
fun enableConfirmButton(name: String?, id: String?, password: String?) {
_enableConfirmButton.value = when {
name == null && id == null && password == null -> true
else -> false
}
}
/** [17] 에딧텍스트가 활성화 되어있을 경우엔ㄴ setTextChangedListener이 잘 작동할 것이기 때문에 아무런 작업을 해줄 필요가 없다.
* 근데 에딧텍스트가 비활성화 되었을 경우에 에딧텍스트에 들어 있는 값이 유효한지 마지막으로 한번 확인해 주어야한다.
* 따라서 hasFocus가 false일 때 어느 editText인지 구분하기 위해 전과 마찬가지로 type을 지정해준다.
* 만약 값이 유효하다면 이때도 null값을 가진다.**/
fun setOnFocusChangedListener(editTextArray: Array<EditText>) {
val type = arrayOf("name", "id", "password")
for (i in type.indices) {
editTextArray[i].setOnFocusChangeListener { _, hasFocus ->
_editTextOnFocus.value = when (hasFocus) {
false -> type[i]
else -> null
}
}
}
}
/** [7] editTextArray에 들어있는 editText의 순서가 이름/아이디/비밀번호이기 때문에
* 동일한 인덱스를 사용하여 코드의 가독성을 높이기 위해 type이라는 배열에
* "name, "id", "password"의 상수들을 저장해 주었다.
* 이 type이라는 배열은 앞으로도 같은 형식으로 계속 사용할 것이다.
* 이제 각각의 에딧텍스트가 변경 될때마다 어떤 에딧텍스트의 값이 변경되었는지 editTextTextChange라는 변수에 저장해 준다.
* 이 값을 이제 initViewModel() 함수에서 관찰해 줄 것이다.**/
fun setTextChangedListener(editTextArray: Array<EditText>) {
val type = arrayOf("name", "id", "password")
for (i in type.indices) {
editTextArray[i].addTextChangedListener {
_editTextTextChange.value = type[i]
}
}
}
}
'내일배움캠프 (스파르타 코딩 클럽) 안드로이드 2기 > TIL' 카테고리의 다른 글
[TIL] 커스텀 다이얼로그 Positive Button 활성화 / (0) | 2024.01.17 |
---|---|
[TIL] ViewPager2 TapLayout (0) | 2024.01.15 |
[TIL] (0) | 2024.01.08 |
[TIL] 트러블슈팅 & RecyclerView 학습 (0) | 2024.01.05 |
[TIL] KPT (1) | 2024.01.02 |