Firebase 사용해서 회원가입/로그인

김재현·2024년 7월 31일

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에 데이터를 전송하는 부분도 작성은 되어있지만 이 부분은 다음에 작성해보겠습니다!

0개의 댓글