안드로이드 로그인 구현하기(DataBindin, LiveData, MVVM)-Android

장욱진·2023년 1월 10일
0

android-study

목록 보기
1/1
post-thumbnail

Databinding

build.gradle (:app) 파일

  • dataBinding 옵션을 true로 추가
  • viewmodel dependencies 추가

Sync Now

android {
	...
	buildFeatures {
    	dataBinding true
	}
}
dependencies {
	implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
}


activity_login.xml

  • as-is
<?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>

MVVM

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

  • viewModel과 databinding 초기화
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)
        }
    }
}

  • 위와 같이 코드를 작성한 후 앱을 실행했다.
    ID와 PWD를 입력하고 로그인을 시도하였지만 viewModel에서 정의한 로그가 찍히지 않을 것이다.
    이유는 .xml에 버튼에 정의된 enabled가 false이기 때문이다.
android:enabled="false"
  • 다음 개발
    ID, PWD 규칙에 맞춰 enabled을 컨트롤 하기 위해 LiveData를 적용할 것이다.


LiveData

build.gradle (:app) 파일

  • liveData dependencies 추가
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
  • 로그인 버튼 제어를 위한 LoginFormState 생성
data class LoginFormState(
    val isDataValid: Boolean = false
)
  • LoginViewModel, LiveData 선언 및 로그인 버튼 제어 메서드 추가
    • loginDataChanged 호출하여 LoginFormState 데이터를 변경해준다
  • LiveData<>는 읽기만 가능, MutableLiveData<>는 변경 가능
    • MutableLiveData<>으로 데이터를 set하고 외부에서 LiveData으로 읽게 개발
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)
        }
    }
}
  • LoginActivity
    • LiveData를 observe 하여 데이터의 변경에 따른 로직 작성
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을 공부한 후 서버에 로그인하는 로직을 추가할 예정이다.


공부중입니다.
감사합니다.

0개의 댓글