Firebase를 이용해서 회원가입을 하고 회원정보를 FireStore Storage에 저장할 수 있도록 설정해보았다.
- 먼저 firebase에서 프로젝트의 이름을 정하고 프로젝트를 설정해준다.
- 저는 Firebase Crashlytics를 사용할 것이기 때문에 Google 애널리틱스를 사용하도록 설정했습니다. (추후에 Crash가 난 이유와 개선점을 Firebase에서 관리할 수 있도록!)
- 계정은 default로 설정해주고
- 프로젝트가 생성되면 다음 화면에서 자신의 프로젝트에 맞게 앱에 Firebase를 추가해주면 되는데 저는 안드로이드를 사용하고 있기 때문에 안드로이드를 추가해주겠습니다.
- 앱 등록에서 패키지명을 추가해줍니다.
(저는 임의로 Test라는 프로젝트를 생성해서 만들어 줬습니다.)
- 그 다음 google-services.json 파일을 Project에 app 디렉토리에 넣어줍니다.
- 해당 plugin과 dependencies는 파이어 베이스의 설명대로 복사 해주시면 됩니다!
- 그러면 사전 설정은 끝납니다!
(추후에 편하게 firebase SDK를 추가하고 싶으시면 tools에서 추가할 수 있습니다.)
- Add the...을 눌러서 편하게 SDK를 app에 추가해줄 수 있습니다.
Domain layer
먼저 layer를 data, domain, ui 영역으로 나누어놨기 때문에 domain 영역에서 AuthRepository를 생성하여 회원 정보에 관한 메서드를 suspend fun으로 설정해놨습니다.
이후 suspend operator fun invoke를 통해 AuthRepository의 login과 signup을 return 하도록 UseCase를 만들어 주었습니다.
LoginUseCase
SignUpUseCase
(반환 값을 사실 Result로 굳이 두지 않아도 됩니다. 그저 성공했을 때와 실패했을 때 예외처리를 두고 싶어서 만들어뒀습니다!)
Data layer
- data 영역의 AuthRepositoryImpl를 통해 메서드를 실체화 해주었습니다.
class AuthRepositoryImpl @Inject constructor( private val db: FirebaseFirestore, private val auth: FirebaseAuth ) : AuthRepository { override suspend fun signUp( email: String, password: String, data: FirebaseUserData ): Result<String> { return try { val result = auth.createUserWithEmailAndPassword(email, password).await() val user = result.user if (user != null) { db.collection("user").document(user.uid).set(data) .addOnSuccessListener { Log.d("debug_signup", "success") } .addOnFailureListener { Log.d("debug_signup", "fail") } Result.success("Success") } else { Result.failure(Exception("Fail")) } } catch (e: FirebaseAuthException) { Result.failure(Exception("FirebaseAuthException: ${e.message}")) } catch (e: FirebaseFirestoreException) { Result.failure(Exception("FirebaseFirestoreException: ${e.message}")) } catch (e: Exception) { Result.failure(Exception("Exception: ${e.message}")) } } override suspend fun logIn(email: String, password: String): Result<String> { return try { auth.signInWithEmailAndPassword(email, password).await() Result.success("success") } catch (e: Exception) { return Result.failure(Exception("Exception: ${e.message}")) } }
- 여기서 FirebaseAuth에서 제공해주는 createUserWithEmailAndPassword() 메서드를 통해서 파라미터로 받은 email과 password를 통해 FirebaseAuth에 계정을 생성할 수 있다.
(await() 함수는 코루틴 함수로 suspend 함수 내에서만 사용이 가능한데 await() 함수가 실행되면 코루틴은 결과가 반환될 때까지 기다리게 되면서 생성된 계정의 uid를 가져올 수 있게 해주었습니다.)
Ui layer
- ui layer에서는 viewmodel을 만들어 constructor로 LoginUseCase와 SignUpUseCase를 받아주고 fragment에서는 view에 대한 설정만 해줄 수 있도록 해주었습니다.
ViewModel
@HiltViewModel class LoginViewModel @Inject constructor( private val logInUseCase: LogInUseCase ) : ViewModel() { private val _loginEvent = Channel<CheckLogin> { } val loginEvent = _loginEvent.receiveAsFlow() fun login(email: String, password: String) { viewModelScope.launch { val loginData = logInUseCase(email = email, password = password) if (loginData.isSuccess) { _loginEvent.send(CheckLogin.LoginSuccess("환영합니다! ${email}님\uD83D\uDC4D")) } else { _loginEvent.send(CheckLogin.LoginFail("이메일 및 비밀번호를 확인해주세요.")) } } } }Fragment
private fun initView() { binding.btnLogin.setOnClickListener { initLogin() collectFlow() } } private fun collectFlow() { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(state = Lifecycle.State.STARTED) { loginViewModel.loginEvent.collect { checkLogin -> when (checkLogin) { is CheckLogin.LoginSuccess -> { Toast.makeText(requireContext(), checkLogin.message, Toast.LENGTH_SHORT) .show() findNavController().navigate(R.id.action_loginFragment_to_mainFragment) } is CheckLogin.LoginFail -> { Toast.makeText(requireContext(), checkLogin.message, Toast.LENGTH_SHORT) .show() } } } } } } private fun initLogin() { val email = binding.etLoginEmail.text.toString() val password = binding.etLoginPassword.text.toString() viewLifecycleOwner.lifecycleScope.launch { loginViewModel.login(email, password) } }
- 예전 팀 프로젝트를 많이 참고하면서 진행하였는데 가장 좋았던 부분이 sealed interface를 사용하여 로그인 성공했을 때와 실패했을 때의 data class를 나누어 동작을 다르게 설정해 줄 수있었던 점이 좋았다.
viewmodel에서 loginEvent에 send한 예외처리 상황을 통해 fragment에서는 when문을 이용하여 loginEvent가 받은 LoginSuccess와 LoginFail일 때의 Toast 메세지를 다르게 설정해주어 sealed interface를 이용해보았다.
내 실력으로는 사실 생각하기 어려웠을 것 같은데 예전 팀 프로젝트를 복기하면서 좋은 아이디어를 얻어 적용해 볼 수 있어서 편했던 것 같다.
특히 회원가입 단계에서 예외처리가 굉장히 많이 들어갔어야 했는데 정말 유용했다.
- 회원가입 실패했을 때 각 상황에 맞게 message를 넣어주어 이 메세지를 view에서 Toast 메세지로 사용할 수 있도록 해주었다.
- test용으로 이메일을 설정하여 회원가입을 하면 다음과 같이 Firebase 프로젝트에 계정이 생성되는 것을 볼 수 있다.
.
.
.
.
.
오늘은 Firebase를 통해 회원가입과 로그인을 구현하는 법을 작성해보았습니다.
아직 코드적으로나 여러 방면으로 부족해서 좀 더 심도있게 공부해보고 계속 열심히 해야겠다는 생각이 많이 듭니다 ㅠㅠ
FireStore에 데이터를 전송하는 부분도 작성은 되어있지만 이 부분은 다음에 작성해보겠습니다!