이제 ViewModel을 만들어, LiveData를 활용하여 Data Binding을 시켜줄 차례다. 시작이 반이라더니, 시작은 역시 그냥 시작인 것 같다. 여전히 한참 남았다. 처음부터 이 아키텍처로 시작했으면 당연히 좀더 빨랐겠지만, 그래도 이왕 만드는 거, 처음부터 차근차근 만들어보는 경험이 스스로에게도 리마인드 하기에 도움이 크게 된다 느껴졌다.
app단 build.gradle에 아래와 같이 의존성을 추가해준다.
build.gradle(:app)
...
def lifecycle_version = "2.6.0-alpha01"
def optional_lifecycle_version = "2.5.1"
def arch_version = "2.1.0"
dependencies {
...
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// optional - helpers for implementing LifecycleOwner in a Service
implementation "androidx.lifecycle:lifecycle-service:$optional_lifecycle_version"
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
implementation "androidx.lifecycle:lifecycle-process:$optional_lifecycle_version"
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"
}
디렉토리에 viewmodel을 추가해준다.
일단은 ViewModel()
을 상속하되, 필요시 AndroidViewModel
로 바꿔 컨텍스트를 가져올 것이다.
그 다음, login_fragment.xml
의 layout
태그와 ConstraintLayout
태그 사이에 LogInViewModel
을 변수로 추가해준다.
login_fragment.xml
<data>
<variable
name="logInViewModel"
type="com.movingroot.toyproject.viewmodel.LogInViewModel" />
</data>
이제, TextInputEditText
들에 2-way binding을 걸어주어 Observing 할 것이다. 이를 위해 우선 LogInViewModel
에 아래와 같이 변수를 추가해주었다.
LogInViewModel.kt
val _idInput = MutableLiveData<String>()
val idInput: LiveData<String> get() = _idInput
val _pwInput = MutableLiveData<String>()
val pwInput: LiveData<String> get() = _pwInput
login_fragment
에서는 다음과 같이 text를 set 해준다.
android:text="@={logInViewModel._idInput}"
단순히 observing 하는 one-way binding과 달리 실시간으로 수정이 되는 데이터를 반영해야 하는 EditText의 특성상 2-way binding 처리해주었으며, 이때 @={}
와 같이 작성해줘야 함에 주의한다.
더하여, 로그인 함수 및 Toast 띄우는 함수도 모두 ViewModel로 이관할 것이다. 이를 위해 아래와 같이 변수들을 ViewModel에 선언해주고, 함수 세팅을 해준다. 클릭 함수를 ViewModel로 가져가 xml에 다이렉트로 링크를 할 것이다. (hideKeyboard는 그냥 남겨둘 것이다. 이것까지 가져올 이유는 없다고 본다. 물론 AndroidViewModel
로 하여 applicationContext
사용을 통해 구현할 수도 있을 것이다.)
LogInViewModel.kt
private val _logInFlag = MutableLiveData<Boolean>()
val logInFlag: LiveData<Boolean> get() = _logInFlag
private val _toast = MutableLiveData<String>()
val toast: LiveData<String> get() = _toast
...
fun logIn() {
if (!validateInputs()) {
_toast.value = "계정 또는 비밀번호를 입력해주세요."
return
}
if (!validatePassword()) {
_toast.value = "비밀번호는 최소 6자리 이상입니다."
return
}
_logInFlag.value = true
}
private fun validateInputs() = idInput.value!!.isNotEmpty() && pwInput.value!!.isNotEmpty()
private fun validatePassword() = pwInput.value!!.length > 5
이제 Fragment를 세팅해주면, UI 요소 변수들은 baseLyt 제외하고 전부 지워준다. 그리고 binding 변수를 아래와 같이 추가하여, Binding.root를 onCreateView
에서 return 받도록 수정한다.
이때, viewModel을 아래와 같이 inline으로 선언해줄 수 있는데, 지연선언을 통한 방식으로는 아래와 같이도 작성해줄 수 있다.
private lateinit var viewModel: LogInViewModel
...
viewModel = ViewModelProvider(this)[LogInViewModel::class.java]
아무튼, Fragment의 코드는 다음과 같다.
LogInFragment.kt
private var _binding: LoginFragmentBinding? = null
private val binding: LoginFragmentBinding get() = _binding!!
private val viewModel: LogInViewModel by viewModels()
...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = LoginFragmentBinding.inflate(inflater, container, false)
initFragment()
setObservers()
return binding.root
}
private fun initFragment() {
_binding?.apply {
lifecycleOwner = viewLifecycleOwner
logInViewModel = viewModel
}
}
private fun setObservers() {
with(viewModel) {
logInFlag.observe(viewLifecycleOwner) {
if (it) {
toList()
}
}
toast.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
makeToast(it)
viewModel.initToast()
}
}
}
}
xml의 버튼에는 android:onClick="@{()->logInViewModel.logIn()}"
리스너를 달아준 후, 실행해보자. 의도한 대로 작동한다. 체크박스는 실제로 해당 기능을 활용하게 될 때 달아줄 생각이다.
'개발일기' 카테고리의 다른 글
8월 한달 토이 프로젝트를 해보자! -10 : Firebase 실패기 (진행중) (0) | 2022.08.16 |
---|---|
8월 한달 토이 프로젝트를 해보자! -09 : Adapter 높이 programmatically 설정해주기 (0) | 2022.08.10 |
8월 한달 토이 프로젝트를 해보자! -07 : submenu (0) | 2022.08.09 |
8월 한달 토이 프로젝트를 해보자! -06 : AppBar (0) | 2022.08.09 |
8월 한달 토이 프로젝트를 해보자! -05 : Adapter Click (0) | 2022.08.09 |