build.gradle (:app) 파일
Sync Now
android {
...
buildFeatures {
dataBinding true
}
}
dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="50dp"
tools:context=".ui.login.LoginActivity">
<!-- ID-->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="43dp"
android:hint="@string/prompt_id"
android:minHeight="45dp"
app:endIconMode="clear_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_pwd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:hint="@string/prompt_pwd"
app:endIconMode="password_toggle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_id"
app:layout_constraintVertical_bias="0.5">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="30dp"
android:layout_marginBottom="15dp"
android:enabled="false"
android:text="@string/action_sign_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_pwd"
app:layout_constraintVertical_bias="0.2" />
<CheckBox
android:id="@+id/chk_auto_login"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/auto_login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_login" />
</androidx.constraintlayout.widget.ConstraintLayout>
.xml파일 parent-Layout 클릭하여 나온 전구메뉴에서 [Convert to data binding layout]으로 parent-Layout을 layout으로 감싸준다.
to-be
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="50dp"
tools:context=".ui.login.LoginActivity">
<!-- ID-->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="43dp"
android:hint="@string/prompt_id"
android:minHeight="45dp"
app:endIconMode="clear_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_pwd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:hint="@string/prompt_pwd"
app:endIconMode="password_toggle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_id"
app:layout_constraintVertical_bias="0.5">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="30dp"
android:layout_marginBottom="15dp"
android:enabled="false"
android:text="@string/action_sign_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_pwd"
app:layout_constraintVertical_bias="0.2" />
<CheckBox
android:id="@+id/chk_auto_login"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/auto_login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_login" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
LoginViewModel
import android.util.Log
import androidx.lifecycle.ViewModel
class LoginViewModel : ViewModel() {
fun requestLogin(uid: String, upwd: String, isAutoLogin: Boolean) {
Log.d("LoginViewModel", "uid : $uid upwd : $upwd isAutoLogin : $isAutoLogin")
}
}
LoginActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.wookoding.android_study.databinding.ActivityLoginBinding
class LoginActivity : AppCompatActivity() {
private lateinit var loginViewModel: LoginViewModel
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
val idEdit = binding.etId
val pwdEdit = binding.etPwd
val btnLogin = binding.btnLogin
val checkBox = binding.chkAutoLogin
loginViewModel = ViewModelProvider(this).get(LoginViewModel::class.java)
btnLogin.setOnClickListener {
loginViewModel.requestLogin(idEdit.text.toString(), pwdEdit.text.toString(), checkBox.isChecked)
}
}
}
android:enabled="false"
build.gradle (:app) 파일
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
data class LoginFormState(
val isDataValid: Boolean = false
)
class LoginViewModel : ViewModel() {
private val _loginForm = MutableLiveData<LoginFormState>()
val loginFormState: LiveData<LoginFormState> = _loginForm
...
fun loginDataChanged(uid: String, upwd: String) {
if (uid.isEmpty() || upwd.isEmpty()) {
// 에러 메세지를 만들어 변경할 경우 isDataValid = false이 필요없음.
_loginForm.value = LoginFormState(isDataValid = false)
} else {
_loginForm.value = LoginFormState(isDataValid = true)
}
}
}
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
loginViewModel.loginFormState.observe(this@LoginActivity, Observer {
val loginState = it ?: return@Observer
// disable login button unless both username / password is valid
btnLogin.isEnabled = loginState.isDataValid
})
idEdit.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
loginViewModel.loginDataChanged(
idEdit.text.toString(),
pwdEdit.text.toString()
)
}
})
pwdEdit.apply {
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
loginViewModel.loginDataChanged(
idEdit.text.toString(),
pwdEdit.text.toString()
)
}
})
setOnEditorActionListener { textView, i, keyEvent ->
when (i) {
EditorInfo.IME_ACTION_DONE ->
loginViewModel.requestLogin(
idEdit.text.toString(),
pwdEdit.text.toString(),
checkBox.isChecked
)
}
false
}
}
...
}
}
DataBindin, LiveData, MVVM을 활용하여 간단하게 로그인을 구현해 봤다.
model 없는 반쪽짜리지만 retrofit2을 공부한 후 서버에 로그인하는 로직을 추가할 예정이다.
공부중입니다.
감사합니다.