0️⃣ Firebase로 Google Login?
사실 많은 앱들에서 소셜 로그인을 지원한다. 나 같은 경우엔 이제 앱에 소셜 로그인 없이 자체 회원가입만 있다면 잘 이용하지 않게 되는 것 같다. 특히, 자체 회원가입을 하더라도 소셜 계정 연동이 없다면 더더욱! 그만큼 소셜 로그인이 앱의 필수 기능으로 자리잡은 만큼 꼭 구현해보고 싶었던 기능이다. (그동안 소셜 로그인을 한 번도 연동해본 적이 없다는 것이 더욱 충격적)
이번 프로젝트에서는 서버 없이 FireStore로만 작업했기 때문에 Firebase Google Login 구현이라고 보면 된다. 차근차근 해보자.
1️⃣ Firebase Setting
먼저 Firebase 세팅을 해주면 된다. 우리 팀의 리더 친구가 세팅 해주었다! 하지만 세팅 자체가 어렵지는 않다.
1. 프로젝트 설정 > 일반 > SHA - 1 설정
여기서 내 프로젝트의 SHA-1 key 값을 가져와 인증서 지문을 등록해주고,
2. Authentication > Google 로그인 추가
Authentication에서 Google 로그인을 추가해주면 된다.
3. google-services.json 파일 추가
마지막으로 google-services.json 파일을 다운로드 받아 app 폴더에 넣어주면 된다.
2️⃣ Google Cloud Console Setting
OAuth 동의 화면을 등록하고, 사용자 인증정보에서 OAuth 클라이언트 ID까지 추가해주면 된다. (그런데 나같은 경우엔 클라이언트 ID가 등록이 안 돼서 그만 두었고 안 해도 잘 됐다. 이유는 왜인지 잘 모르겠다...)
⬇️⬇️ 원인 파악 완료! ⬇️⬇️
클라이언트 ID가 등록을 안 해도 잘 됐었는데 드디어 원인을 파악했다. Firebase 측에서 자동으로 생성했기 때문이다!
짜잔! 혹시 난 한 적이 없는데 잘 된다~ 싶으면 가서 확인해보도록 하자. 프로젝트가 새로 등록 되면서 OAuth 클라이언트 ID가 모두 등록 되어있을 것이다.
3️⃣ build.gradle Setting
1. app 단위의 gradle 세팅
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.google.services)
}
dependencies {
...
// firebase
implementation(platform(libs.firebase.bom)) //구글 로그인 필수
implementation(libs.firebase.auth) //구글 로그인 필수
implementation(libs.play.services.auth) //구글 로그인 필수
implementation(libs.firebase.analytics)
implementation(libs.firebase.firestore)
...
}
2. project 단위의 gradle 세팅
plugins {
...
alias(libs.plugins.google.services) apply false
}
이렇게 gradle 세팅 해주면 끝!
우리 프로젝트는 버전 관리 파일이 따로 있었기 때문에 만약 이 글을 참고한다면 가장 최신 firebase 버전을 추가해주면 된다.
4️⃣ Firebase 코드 작성하기
xml 파일은 생략하겠다. 각자 예쁜 UI에서 버튼을 연결할 수 있도록 해보자. (다만, 구글 로그인에서 제시하는 디자인 가이드를 따라야 한다.)
1. SignInActivity
class SignInActivity : AppCompatActivity() {
private lateinit var auth: FirebaseAuth
val firestoreDB = FirebaseFirestore.getInstance()
private lateinit var googleSignInClient: GoogleSignInClient
private val signInBinding by lazy { ActivitySignInBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(signInBinding.root)
//auth 객체 초기화
auth = FirebaseAuth.getInstance()
//GoogleSignInClient 객체 초기화
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) //기본 로그인 방식 사용
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso) // this 부분엔 context 값이 들어감
// 구글 로그인 버튼에 listener 부착
signInBinding.ivGoogle.setOnClickListener {
googleLogin()
}
}
// 1. Google Login
private fun googleLogin() {
// 구글 로그인할 수 있는 팝업을 띄워주는 Intent
val signInIntent = googleSignInClient.signInIntent
googleLauncher.launch(signInIntent)
}
// 2. Google Login Popup Dialog
private val googleLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
try {
// 구글 로그인이 성공하면, 로그인한 구글 계정을 받아옴
val account = task.getResult(ApiException::class.java)!!
firebaseAuthWithGoogle(account.idToken!!)
} catch (e: ApiException) {
// Google 로그인 실패
Toast.makeText(this, "Google 로그인에 실패했습니다.", Toast.LENGTH_SHORT).show()
}
}
// 3. Google Account를 Firebase로 전송
private fun firebaseAuthWithGoogle(idToken: String) {
val credential = GoogleAuthProvider.getCredential(idToken, null)
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// 성공적으로 구글 계정의 token 값을 받아온 경우
Toast.makeText(this, "환영합니다, ${user?.displayName}!", Toast.LENGTH_SHORT).show()
// 로그인 이후 화면 전환
startActivity(Intent(this, MainActivity::class.java))
finish()
} else {
// 로그인 실패
Toast.makeText(this, "Firebase 인증에 실패했습니다.", Toast.LENGTH_SHORT).show()
}
}
}
}
- FirebaseAuth를 lateinit으로 선언 해주고, onCreate()에서 값을 초기화 해준다.
- GoogleSignInClient를 lateinit으로 선언해주고, 동일하게 onCreate()에서 값을 초기화해준다.
- gso 객체를 만들어주고 이를 context값과 함께 넣어주어야 한다.
- googleLogin 함수를 만들어준다.
- 이곳에서는 구글로그인을 눌렀을 때 구글 계정에 로그인 하거나 (디바이스에 등록된 구글 계정이 없는 경우), 로그인된 구글 계정을 선택할 수 있는 팝업 화면을 띄워준다.
- googleLauncher 함수를 만들어준다.
- 이곳에서는 구글 로그인이 성공했을 경우 계정 정보를 받아오는 역할을 수행한다.
- 이때 firebaseAuthWithGoogle 함수로 계정 정보의 idToken값을 넘겨준다.
- firebaseAuthWithGoogle 함수를 만들어준다.
- 이곳에서는 성공적으로 받아온 구글 계정을 firebase로 넘겨주는 역할을 수행한다.
- task.isSuccessful 이면 로그인 이후 수행해야할 로직을 작성한다.
- else 구문에는 firebase로 데이터 전송에 실패한 경우이기 때문에 에러처리를 해주면 된다.
이렇게 하면 손쉽게 구글 로그인 구현이 가능하다!
cf) 만약 Google 계정을 FireStore에 전송하여 저장하고 싶다면?
...
val firestoreDB = FirebaseFirestore.getInstance()
...
private fun firebaseAuthWithGoogle(idToken: String) {
val credential = GoogleAuthProvider.getCredential(idToken, null)
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// 성공적으로 구글 계정의 token 값을 받아온 경우
//추가
val user = auth.currentUser
val uId = user?.uid.toString()
val name = user?.displayName.toString()
...
val userRef = firestoreDB.collection("users").document(uId)
userRef.get().addOnSuccessListener { document ->
if (document.exists()) {
// 기존 유저
// 코드 작성 필요...
Log.d("기존 유저 정보 업데이트", "Loaded user data userData")
} else {
val firstUser = hashMapOf(
"uID" to uId,
"name" to name,
...
)
userRef.set(firstUser)
.addOnSuccessListener { Log.d("User Data 전송 성공", "User data is successfully written!") }
.addOnFailureListener { exception -> Log.w("User Data 전송 실패", "Error writing document", exception) }
}
}
//추가 끝
Toast.makeText(this, "환영합니다, ${user?.displayName}!", Toast.LENGTH_SHORT).show()
// 로그인 이후 화면 전환
startActivity(Intent(this, MainActivity::class.java))
finish()
} else {
// 로그인 실패
Toast.makeText(this, "Firebase 인증에 실패했습니다.", Toast.LENGTH_SHORT).show()
}
}
}
앞서 말했듯이 이번 프로젝트에서는 서버가 없기 때문에 Google Login으로 받아온 계정 정보를 FireStore에 저장하는 코드가 필요했다. 또한 최초 로그인과 재로그인한 유저를 구분할 필요가 있었기 때문에 document가 존재하는지 여부를 따져 유저 정보를 업데이트 하거나, 새로운 데이터를 넣어주는 코드로 작성했다.
아쉽게도, document가 존재할 때의 코드는 에러가 나서 아직 작성하지 못했다. 에러를 해결 하면 새로운 포스팅으로 다시 기록하겠다.
5️⃣ 그런데 내가 만났던 오류는?
이렇게 적어놓고 보면 제법 간단하고 쉽지만, 사실 마냥 순탄하게만 진행된 것은 아니다. SHA-1 키값이 일치하지 않는 오류가 발생했고, 이제 저 코드를 클린 아키텍처에 맞추어 리팩토링 중에 있다. 아래 포스팅을 통해 각각 어떻게 해결했는지 확인해보자!
1. SHA-1 키값 오류 해결
2. 클린 아키텍처로 리팩토링 하기
❗ 출처
참고 사이트1 : https://velog.io/@duridudu/일일이-androidkotlin-구글-로그인-연동하기
'Android' 카테고리의 다른 글
Android와 선언형 UI : Jetpack Compose (5) | 2024.10.15 |
---|---|
Android에서 Firebase Google Login 구현하기 (feat. 클린 아키텍처 리팩토링) (5) | 2024.09.02 |
Hilt로 Context 사용하기 (feat. OkHttpClient cache) (0) | 2024.08.29 |
Android에서 CustomDialog 만들기 (0) | 2024.08.27 |
Android의 4대 구성요소 알아보기 (0) | 2024.08.14 |