클린 아키텍쳐를 검색해서 읽어보고 패키지를 처음부터 다시 정리해보려고 하면서 코드도 천천히 수정해 나가려고 하고 있는데요, 와중에 LiveData를 활용해서 Login의 상태변화를 관차할때 좀 막히는 부분이 있어서 질문드립니다.
CalendarFragment를 startDestination으로 설정하고 앱시작시 여기서 로그인 상태를 검사합니다.
값이 없으면 LoginFragment로 가서 로그인을 진행합니다.
CalendarFragment에서는 크게 문제가 없는것같은데 LoginFragment에서
로그인버튼을 누르면 LiveData를 이용해서 상태를 관찰을 하게 되는데 자꾸 두번(?) 진행하게됩니다.
현재 로그인 버튼 클릭 리스너 내부에 뷰모델을 observe하도록 설정해놨습니다(이부분은 개발자 문서보고 했습니다..) 그러니까 로그인을 하기위해 버튼을 누르면 뷰모델을 observe하겠죠. 이때 문제가 있는데 버튼을 클릭하면 UNAUTHENTICATED 구문이 진행되고 AUTHENTICATED 구문이 진행됩니다.
정상적으로라면 아이디비밀번호 값이 정확하다면 AUTHENTICATED 구문만 진행되어야하죠.
로그인버튼 클릭리스너내에서 observe 하도록 설정해놨다고 했는데 일반적인 방법대로
observe 코드를 따로 분리하여 진행도 해보았습니다.
이경우에는 앱실행시 CalendarFragment에서 로그인 상태를 검사하고
LoginFragment로 넘어왔을때 바로 UNAUTHENTICATED구문이 한번 진행되었습니다.
그런데 이 경우에는 로그인 버튼을 누르면 올바르게 AUTHENTICATED 구문을 바로 실행하더라구요.
왜 이런걸까요? 그리고 어떻게 해야할까요?
+) 그리고 클린아키텍쳐를 사용한 코드들을 보니까 Repository에도 LiveData를 두고 ViewModel에서도
LiveData를 두어서 뷰모델에서 Repository의 LiveData를 참조하는 방식으로 사용하던데
Repository에 LiveData를 두어도 상관이 없나요?
Repository
class AuthenticationRepository {
private val databaseRef: DatabaseReference =
FirebaseDatabase.getInstance().getReference("LightWeight") // 실시간 데이터 베이스
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val userLiveData: MutableLiveData<FirebaseUser?> = MutableLiveData<FirebaseUser?>()
fun getUserData() : MutableLiveData<FirebaseUser?> {
userLiveData.postValue(firebaseAuth.currentUser)
userLiveData.postValue(null) // 한번 로그인하면 계속 user 값이 있기때문에 임시방편으로 null로 강제 설정
// firebaseAuth.signOut()
return userLiveData
}
fun login(email: String, pwd: String) {
firebaseAuth.signInWithEmailAndPassword(email, pwd)
.addOnCompleteListener { task ->
if(task.isSuccessful) { // 로그인 성공
userLiveData.postValue(firebaseAuth.currentUser)
}
else {
}// 실패
}
}
}
ViewModel
class LoginViewModel : ViewModel() {
private val repository: AuthenticationRepository = AuthenticationRepository()
val authenticationState = repository.getUserData().map { user ->
if (user != null) {
AuthenticationState.AUTHENTICATED
} else {
AuthenticationState.UNAUTHENTICATED
}
}
sealed class AuthenticationState {
object AUTHENTICATED: AuthenticationState()
object UNAUTHENTICATED : AuthenticationState()
}
fun login(email: String, pwd: String) : LiveData<AuthenticationState> {
repository.login(email, pwd)
return authenticationState
}
}
CalendarFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loginVM.authenticationState.observe(viewLifecycleOwner) { authState ->
when(authState) {
LoginViewModel.AuthenticationState.AUTHENTICATED ->
Toast.makeText(context, "로그인 되어있음",Toast.LENGTH_SHORT).show()
else -> {
Toast.makeText(context, "로그인 안되어",Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.login_nav)
}
}
}
}
LoginFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 이전 destination(CalendarFragment)의 BackStackEntry에 접근하여 결과 설정
savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle
savedStateHandle.set(LOGIN_SUCCESSFUL, false)
binding.btnLogin.setOnClickListener { _ ->
login()
}
}
private fun login() {
//로그인 요청
var email: String = binding.etId.text.toString()
var pwd: String = binding.etPwd.text.toString()
loginVM.login(email, pwd).observe(viewLifecycleOwner) { test ->
when(test) {
LoginViewModel.AuthenticationState.AUTHENTICATED -> {
savedStateHandle.set(LOGIN_SUCCESSFUL, true)
findNavController().popBackStack()
Toast.makeText(context, "성공", Toast.LENGTH_SHORT).show()
}
LoginViewModel.AuthenticationState.UNAUTHENTICATED ->
Toast.makeText(context, "실패", Toast.LENGTH_SHORT).show()
}
}
}