맨땅에 코딩
'학습의 숲' 소소한 개발일지 - 회원 가입, 로그인, 로그아웃 본문
UserRepository
🍀 Firebase Realtime Database를 통해 사용자 정보를 가져오고, 사용자 정보를 데이터베이스에 저장하는 기능을 제공한다. 이를 통해 앱의 다른 부분에서 사용자 정보를 쉽게 관리할 수 있다.
package com.example.forestlearning.repository
import androidx.lifecycle.MutableLiveData
import com.example.forestlearning.UserData
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
class UserRepository {
// Firebase 데이터베이스 인스턴스 초기화
val database = Firebase.database
// Firebase 데이터베이스의 "Users" 레퍼런스에 대한 참조 생성
private val usersRef = database.getReference("Users")
// 사용자 이름을 가져오는 함수
fun getName(uid: String, name: MutableLiveData<String>) {
// "Users"에 있는 해당 사용자의 "name" 레퍼런스 가져오기
val nameRef = usersRef.child(uid).child("name")
nameRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
// 데이터 변경 이벤트가 발생하면, 이름을 업데이트
val newName = snapshot.value.toString()
name.postValue(newName)
}
override fun onCancelled(error: DatabaseError) {
// 데이터 가져오기가 취소되었을 때의 처리
}
})
}
// 사용자 이메일을 가져오는 함수
fun getEmail(uid: String, email: MutableLiveData<String>) {
// "Users"에 있는 해당 사용자의 "email" 레퍼런스 가져오기
val emailRef = usersRef.child(uid).child("email")
emailRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
// 데이터 변경 이벤트가 발생하면, 이메일을 업데이트
val newEmail = snapshot.value.toString()
email.postValue(newEmail)
}
override fun onCancelled(error: DatabaseError) {
// 데이터 가져오기가 취소되었을 때의 처리
}
})
}
// 데이터베이스에 사용자 정보를 저장하는 함수
fun postUser(name: String, email: String, uId: String) {
// 사용자 데이터 생성
val user = UserData(name, email, uId)
// "Users"에 사용자 데이터 저장
usersRef.child(uId).setValue(user)
}
}
- database: Firebase Realtime Database의 인스턴스다. 이를 통해 데이터베이스에 접근할 수 있다.
- usersRef: "Users"라는 이름의 데이터베이스 레퍼런스를 가리킨다. 이를 통해 "Users" 아래에 저장된 사용자 정보에 접근할 수 있다.
- getName(): 특정 사용자의 이름을 가져오는 함수다. 사용자의 UID를 파라미터로 받아 해당 사용자의 이름 데이터에 대한 레퍼런스를 가져온다. 이 레퍼런스에 ValueEventListener를 추가하여 데이터가 변경될 때마다 이름을 업데이트한다.
- getEmail(): 특정 사용자의 이메일을 가져오는 함수다. 사용자의 UID를 파라미터로 받아 해당 사용자의 이메일 데이터에 대한 레퍼런스를 가져온다. 이 레퍼런스에 ValueEventListener를 추가하여 데이터가 변경될 때마다 이메일을 업데이트한다.
- postUser(): 데이터베이스에 사용자 정보를 저장하는 함수다. 사용자의 이름, 이메일, UID를 파라미터로 받아 UserData 객체를 생성하고, 이를 "Users" 레퍼런스 아래에 저장한다.
UserViewModel
🍀 사용자의 정보를 LiveData로 관리하고, 이 정보를 Firebase Realtime Database와 동기화하는 역할을 한다. 이를 통해 앱의 다른 부분에서 사용자 정보를 쉽게 관찰하고 업데이트할 수 있다.
package com.example.forestlearning.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.forestlearning.repository.UserRepository
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
class UserViewModel : ViewModel() {
// UserRepository 인스턴스 생성
private val repository = UserRepository()
// 사용자 이메일을 저장하는 MutableLiveData 생성
private val _email = MutableLiveData("")
val email: LiveData<String> get() = _email
// 사용자 이름을 저장하는 MutableLiveData 생성
private val _name = MutableLiveData("")
val name: LiveData<String> get() = _name
// 사용자 UID를 저장하는 MutableLiveData 생성
private val _uid = MutableLiveData("")
val uid: LiveData<String> get() = _uid
// 사용자 정보를 설정하고 Firebase Realtime Database에 저장하는 함수
fun setUser(name: String, email: String, uid: String) {
// 사용자 정보가 변경되었을 경우에만 Firebase Realtime Database에 저장
if (_name.value != name || _email.value != email || _uid.value != uid) {
_name.value = name
_email.value = email
_uid.value = uid
repository.postUser(name, email, uid)
}
}
// Firebase Realtime Database로부터 사용자 정보를 가져오는 함수
fun fetchUser(uid: String) {
// Firebase Realtime Database로부터 사용자 정보를 가져오기
val db = FirebaseDatabase.getInstance()
db.getReference("Users").child(uid).addListenerForSingleValueEvent(object:
ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
// Firebase Realtime Database에서 가져온 사용자 정보를 LiveData에 저장
_name.value = snapshot.child("name").value.toString()
_email.value = snapshot.child("email").value.toString()
_uid.value = uid
// UserRepository를 통해 Firebase Realtime Database에 사용자 이름과 이메일 저장
repository.getName(uid, _name)
repository.getEmail(uid, _email)
}
override fun onCancelled(error: DatabaseError) {
// 데이터 가져오기 취소되었을 때의 처리
}
})
}
}
- repository: UserRepository의 인스턴스다. 이를 통해 사용자 정보를 Firebase Realtime Database에 저장하거나 가져올 수 있다.
- _email, _name, _uid: 사용자의 이메일, 이름, UID를 저장하는 MutableLiveData다. MutableLiveData는 값이 변경될 수 있는 LiveData다. LiveData는 데이터의 변경을 관찰하고 UI를 업데이트하는데 사용된다.
- email, name, uid: _email, _name, _uid의 getter다. 이를 통해 UserViewModel 외부에서는 LiveData를 읽기 전용으로 접근할 수 있다.
- setUser(): 사용자 정보를 설정하고 Firebase Realtime Database에 저장하는 함수다. 사용자의 이름, 이메일, UID를 파라미터로 받아 해당 정보가 변경되었을 경우에만 MutableLiveData와 Firebase Realtime Database에 저장한다.
- fetchUser(): Firebase Realtime Database로부터 사용자 정보를 가져오는 함수다. 사용자의 UID를 파라미터로 받아 해당 사용자의 정보를 Firebase Realtime Database에서 가져오고, 이를 MutableLiveData에 저장한다.
SingInFragment
🍀 사용자가 입력한 이름, 이메일, 비밀번호를 Firebase에 등록하여 회원 가입을 구현하고, 회원 가입이 성공적으로 이루어지면 이메일 인증 메일을 전송하는 로직을 가지고 있다. Firebase Authentication 서비스의 특성을 활용하여 사용자 인증을 쉽게 구현할 수 있도록 한다.
package com.example.forestlearning
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.example.forestlearning.databinding.FragmentSignInBinding
import com.example.forestlearning.viewmodel.UserViewModel
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
class SignInFragment : Fragment() {
private var binding: FragmentSignInBinding? = null
private val mAuth : FirebaseAuth = Firebase.auth
val viewModel: UserViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentSignInBinding.inflate(inflater, container, false)
binding?.signInEndButton?.setOnClickListener {
val name = binding?.editName?.text.toString().trim()
val email = binding?.editEmail?.text.toString().trim()
val password = binding?.editPassword?.text.toString().trim()
signIn(name, email, password)
}
binding?.signInNoButton?.setOnClickListener {
findNavController().navigate(R.id.action_signInFragment2_to_loginFragment2)
}
return binding?.root
}
//회원 가입 함수
private fun signIn(name: String, email: String, password: String) {
mAuth.createUserWithEmailAndPassword(email, password)?.addOnCompleteListener { task ->
if (task.isSuccessful) {
mAuth.currentUser?.sendEmailVerification()?.addOnCompleteListener { sendTask ->
if (sendTask.isSuccessful) {
//회원가입 성공시 실행
Toast.makeText(requireContext(), "회원가입에 성공했습니다. 이메일 인증 후 로그인이 가능합니다.", Toast.LENGTH_SHORT).show()
viewModel.setUser(name, email, mAuth.currentUser?.uid ?: "")
findNavController().navigate(R.id.action_signInFragment2_to_loginFragment2)
} else {
Toast.makeText(requireContext(), "이메일 전송에 실패했습니다.", Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.action_signInFragment2_to_loginFragment2)
}
}
} else {
Toast.makeText(requireContext(), "회원가입에 실패했습니다.", Toast.LENGTH_SHORT).show()
}
}?.addOnFailureListener {
Toast.makeText(requireContext(), "회원가입에 실패했습니다.", Toast.LENGTH_SHORT).show()
}
}
override fun onDestroy() {
super.onDestroy()
binding = null
}
}
- binding: FragmentSignInBinding 인스턴스를 가지고 있으며, 이를 통해 레이아웃에 정의된 뷰들에 접근할 수 있다.
- mAuth: Firebase Authentication의 인스턴스다. 이를 통해 Firebase와의 인증 관련 작업을 수행할 수 있다.
- viewModel: UserViewModel의 인스턴스를 가지고 있으며, 이를 통해 사용자 데이터를 관리한다.
- onCreateView(): 프래그먼트의 뷰가 처음 생성될 때 호출되는 메소드이다. 여기서 레이아웃을 inflate하고, 버튼의 클릭 리스너를 설정한다.
- signInEndButton: 회원 가입을 완료하는 버튼이다. 클릭하면 입력된 이름, 이메일, 비밀번호를 가져와 signIn() 함수를 호출하게 된다.
- signInNoButton: 회원 가입을 취소하는 버튼이다. 클릭하면 LoginFragment로 이동한다.
- signIn(): 회원 가입 로직을 수행하는 함수이다. createUserWithEmailAndPassword() 함수를 통해 Firebase에 사용자를 등록하고, 성공적으로 등록되면 이메일 인증 메일을 전송한다. 이메일 인증 메일 전송이 성공하면 UserViewModel에 사용자 정보를 저장하고, LoginFragment로 이동한다.
- onDestroy(): 프래그먼트가 소멸될 때 호출되는 메소드이다. 여기서 binding을 null로 설정하여 메모리 누수를 방지한다.
Authentication
🍀 Firebase Authentication을 통해 로그인 상태를 관리하고, 현재 로그인한 사용자의 이메일을 저장하는 역할을 한다. 이를 통해 앱의 다른 부분에서 현재 로그인 상태를 쉽게 확인하고, 로그인한 사용자의 정보를 가져올 수 있다.
package com.example.forestlearning
import androidx.multidex.MultiDexApplication
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
class Authentication: MultiDexApplication() {
//companion 블록 = 클래스의 인스턴스를 생성하지 않고도 접근할 수 있는 정적 멤버를 정의하는데 사용
companion object {
var auth: FirebaseAuth = Firebase.auth
//로그인 확인 여부로 사용하는 email 변수
var email: String? = null
//로그인 여부를 확인하는 함수
fun checkLogin(): Boolean {
//firebase에 등록한 사용자 정보 불러오기
val currentUser = auth.currentUser
return currentUser?.let {
//유저 정보가 있으면 email 가져오기
email = currentUser.email
//로그인 되어있는 경우
currentUser.isEmailVerified
} ?: let {
false
}
}
}
}
- MultiDexApplication: Android의 멀티덱스 지원을 위한 기본 애플리케이션 클래스이다. 멀티덱스를 사용하는 이유는, 앱이 참조하는 메소드의 총 수가 65,536개를 초과하는 경우 발생하는 문제를 해결하기 위함이다.
- auth: Firebase Authentication의 인스턴스이다. 이를 통해 Firebase와의 인증 관련 작업을 수행할 수 있다.
- email: 현재 로그인한 사용자의 이메일을 저장하는 변수다. 이 변수를 통해 어느 사용자가 로그인했는지 알 수 있다.
- checkLogin(): 현재 사용자의 로그인 상태를 체크하는 함수다. auth.currentUser를 통해 현재 로그인한 사용자의 정보를 가져올 수 있다. 만약 로그인한 사용자가 있고, 그 사용자가 이메일 인증을 완료한 상태라면 true를 반환하며, 그렇지 않다면 false를 반환한다.
- Companion object: 코틀린에서는 static 키워드가 없는 대신 companion object를 사용하여 정적 멤버를 정의할 수 있다. 이를 통해 인스턴스를 생성하지 않고도 해당 멤버들에 접근할 수 있다.
LoginFragment
🍀 사용자가 입력한 이메일과 비밀번호로 Firebase에 로그인을 시도하고, 로그인이 성공적으로 이루어지면 이메일 인증을 확인한 후 사용자 정보를 UserViewModel에 저장하는 로직을 가지고 있다. Firebase Authentication 서비스의 특성을 활용하여 사용자 인증을 쉽게 구현할 수 있도록 한다.
package com.example.forestlearning
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.example.forestlearning.databinding.FragmentLoginBinding
import com.example.forestlearning.Authentication.Companion.auth
import com.example.forestlearning.viewmodel.UserViewModel
import com.google.firebase.auth.FirebaseAuth
class LoginFragment : Fragment() {
private var binding: FragmentLoginBinding? = null
val viewModel: UserViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentLoginBinding.inflate(inflater, container, false)
//회원가입 버튼을 누르면 회원가입 화면으로 이동
binding?.signInButton?.setOnClickListener {
findNavController().navigate(R.id.action_loginFragment2_to_signInFragment2)
}
//로그인 버튼 이벤트
binding?.loginButton?.setOnClickListener {
val email = binding?.inputEmail?.text.toString().trim()
val password = binding?.inputPassword?.text.toString().trim()
if (binding?.inputEmail?.text?.isEmpty() == false && binding?.inputPassword?.text?.isEmpty() == false) {
login(email, password)
} else {
Toast.makeText(requireContext(), "이메일과 비밀번호를 입력해주세요.", Toast.LENGTH_SHORT).show()
}
}
return binding?.root
}
//로그인 함수
private fun login(email: String, password: String) {
auth.signInWithEmailAndPassword(email, password).addOnCompleteListener { task ->
if (task.isSuccessful) {
// 로그인 성공시 실행
if (Authentication.checkLogin()) {
// 이메일 인증이 된 경우
Authentication.email = email
// 현재 로그인된 사용자의 UID 가져오기
val currentUser = FirebaseAuth.getInstance().currentUser
val uid = currentUser?.uid
uid?.let { viewModel.fetchUser(it) } // 뷰모델에 개인정보 저장
Toast.makeText(requireContext(), "로그인에 성공했습니다.", Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.action_loginFragment2_to_mainActivity)
} else{
// 이메일 인증이 안 된 경우
Toast.makeText(requireContext(), "이메일 인증에 실패했습니다.", Toast.LENGTH_SHORT).show()
}
} else {
// 로그인 실패시 실행
Toast.makeText(requireContext(), "로그인에 실패했습니다.", Toast.LENGTH_SHORT).show()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
- binding: FragmentLoginBinding 인스턴스를 가지고 있으며, 이를 통해 레이아웃에 정의된 뷰들에 접근할 수 있다.
- viewModel: UserViewModel의 인스턴스를 가지고 있으며, 이를 통해 사용자 데이터를 관리한다.
- onCreateView(): 프래그먼트의 뷰가 처음 생성될 때 호출되는 메소드이다. 여기서 레이아웃을 inflate하고, 버튼의 클릭 리스너를 설정한다.
- signInButton: 회원 가입 화면으로 이동하는 버튼이다. 클릭하면 SignInFragment로 이동한다.
- loginButton: 로그인을 시도하는 버튼입니다. 클릭하면 입력된 이메일과 비밀번호를 가져와 login() 함수를 호출한다.
- login(): 로그인 로직을 수행하는 함수이다. signInWithEmailAndPassword() 함수를 통해 Firebase에 로그인을 시도하고, 성공적으로 로그인되면 UserViewModel에 사용자 정보를 저장하고, MainActivity로 이동한다.
- onDestroyView(): 프래그먼트의 뷰가 소멸될 때 호출되는 메소드이다. 여기서 binding을 null로 설정하여 메모리 누수를 방지한다.
LogoutFragment
🍀 사용자가 로그아웃 버튼을 누르면 Firebase Authentication의 signOut() 메소드가 호출되어 사용자를 로그아웃시키고, Navigation 컴포넌트의 navigate() 메소드를 호출하여 HostActivity로 이동한다. 이 과정에서 ViewModel을 사용하여 사용자 데이터를 관리하고, Data Binding을 사용하여 UI를 업데이트한다.
package com.example.forestlearning
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.example.forestlearning.databinding.FragmentLogoutBinding
import com.example.forestlearning.viewmodel.UserViewModel
class LogoutFragment : Fragment() {
private var binding: FragmentLogoutBinding? = null
val viewModel: UserViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// FragmentLogoutBinding을 인플레이트하여 binding 객체를 생성
binding = FragmentLogoutBinding.inflate(inflater, container, false)
// 현재 로그인한 사용자의 ID를 가져오기
val uid = Authentication.auth.currentUser?.uid
// uid가 null이 아니라면, 해당 uid의 사용자 정보를 가져오기
if (uid != null) {
viewModel.fetchUser(uid)
}
// 사용자 이름이 변경될 때마다 텍스트 뷰의 내용을 업데이트
viewModel.name.observe(viewLifecycleOwner){ newName ->
binding?.txtName?.text = "$newName 님"
}
// 로그아웃 버튼을 클릭하면 로그아웃 처리를 하고, 로그인 화면으로 이동
binding?.logoutButton?.setOnClickListener {
Authentication.auth.signOut()
Authentication.email = null
findNavController().navigate(R.id.action_logoutFragment_to_hostActivity)
}
return binding?.root
}
override fun onDestroy() {
super.onDestroy()
binding = null
}
}
- 변수 선언:
- binding: FragmentLogoutBinding 타입의 변수로, 이 변수를 통해 레이아웃에 있는 뷰에 접근할 수 있다.
- viewModel: UserViewModel을 activityViewModels()를 통해 가져오며, 이 ViewModel은 activity 수명 주기를 따른다. 이는 여러 Fragment가 같은 ViewModel 인스턴스를 공유할 수 있게 해준다.
- onCreateView(): Fragment의 뷰가 처음 생성될 때 호출되는 메소드입니다. 레이아웃 XML을 인플레이트하고, 사용자 데이터를 가져오며, 로그아웃 버튼의 클릭 리스너를 설정한다.
- FragmentLogoutBinding.inflate(): XML 레이아웃 파일을 인플레이트하여 binding 객체를 생성한다.
- Authentication.auth.currentUser?.uid: 현재 로그인한 사용자의 ID를 가져온다.
- viewModel.fetchUser(uid): 사용자 ID를 이용해 해당 사용자의 정보를 가져온다.
- viewModel.name.observe(): LiveData 객체인 viewModel.name의 변경을 관찰한다. 사용자 이름이 변경될 때마다 UI를 업데이트한다.
- logoutButton.setOnClickListener(): 로그아웃 버튼이 클릭되면 사용자를 로그아웃시키고 HostActivity로 이동한다.
- onDestroy(): Fragment가 파괴될 때 호출되는 메소드이다. 여기서는 binding 객체를 null로 설정하여 메모리 누수를 방지한다.
'KAU 2023 (2학년) > 객체지향프로그래밍' 카테고리의 다른 글
'학습의 숲' 소소한 개발일지 - 과일 누적 랭킹 (0) | 2024.07.25 |
---|---|
'학습의 숲' 소소한 개발일지 - 나의 시간표 (0) | 2024.07.25 |
'학습의 숲' 소소한 개발일지 - 인트로 (0) | 2024.07.25 |
객체지향프로그래밍 실화인가 - '학습의 숲' (0) | 2024.07.09 |
객체지향프로그래밍 실화인가 - 과목 소개 및 수강 후기 (0) | 2024.06.26 |